diff --git a/native/calendar/loading-indicator.react.js b/native/calendar/loading-indicator.react.js
index 52e4f7212..730588b3d 100644
--- a/native/calendar/loading-indicator.react.js
+++ b/native/calendar/loading-indicator.react.js
@@ -1,34 +1,34 @@
// @flow
import * as React from 'react';
import { ActivityIndicator, StyleSheet, Platform } from 'react-native';
import Icon from 'react-native-vector-icons/Feather';
import type { LoadingStatus } from 'lib/types/loading-types';
type Props = {
- loadingStatus: LoadingStatus,
- color: string,
- canUseRed: boolean,
+ +loadingStatus: LoadingStatus,
+ +color: string,
+ +canUseRed: boolean,
};
function LoadingIndicator(props: Props): React.Node {
if (props.loadingStatus === 'error') {
const colorStyle = props.canUseRed
? { color: 'red' }
: { color: props.color };
return ;
} else if (props.loadingStatus === 'loading') {
return ;
} else {
return null;
}
}
const styles = StyleSheet.create({
errorIcon: {
fontSize: 16,
paddingTop: Platform.OS === 'android' ? 6 : 4,
},
});
export default LoadingIndicator;
diff --git a/native/calendar/thread-picker-modal.react.js b/native/calendar/thread-picker-modal.react.js
index ba07f27db..90e482b34 100644
--- a/native/calendar/thread-picker-modal.react.js
+++ b/native/calendar/thread-picker-modal.react.js
@@ -1,99 +1,99 @@
// @flow
import invariant from 'invariant';
import * as React from 'react';
import { StyleSheet } from 'react-native';
import { useDispatch } from 'react-redux';
import {
createLocalEntry,
createLocalEntryActionType,
} from 'lib/actions/entry-actions';
import { threadSearchIndex } from 'lib/selectors/nav-selectors';
import { onScreenEntryEditableThreadInfos } from 'lib/selectors/thread-selectors';
import Modal from '../components/modal.react';
import ThreadList from '../components/thread-list.react';
import { RootNavigatorContext } from '../navigation/root-navigator-context';
import type { RootNavigationProp } from '../navigation/root-navigator.react';
import type { NavigationRoute } from '../navigation/route-names';
import { useSelector } from '../redux/redux-utils';
import { waitForInteractions } from '../utils/timers';
export type ThreadPickerModalParams = {
presentedFrom: string,
dateString: string,
};
type Props = {
- navigation: RootNavigationProp<'ThreadPickerModal'>,
- route: NavigationRoute<'ThreadPickerModal'>,
+ +navigation: RootNavigationProp<'ThreadPickerModal'>,
+ +route: NavigationRoute<'ThreadPickerModal'>,
};
function ThreadPickerModal(props: Props): React.Node {
const {
navigation,
route: {
params: { dateString },
},
} = props;
const viewerID = useSelector(
state => state.currentUserInfo && state.currentUserInfo.id,
);
const nextLocalID = useSelector(state => state.nextLocalID);
const dispatch = useDispatch();
const rootNavigatorContext = React.useContext(RootNavigatorContext);
const threadPicked = React.useCallback(
(threadID: string) => {
invariant(
dateString && viewerID && rootNavigatorContext,
'inputs to threadPicked should be set',
);
rootNavigatorContext.setKeyboardHandlingEnabled(false);
dispatch({
type: createLocalEntryActionType,
payload: createLocalEntry(threadID, nextLocalID, dateString, viewerID),
});
},
[rootNavigatorContext, dispatch, viewerID, nextLocalID, dateString],
);
React.useEffect(
() =>
navigation.addListener('blur', async () => {
await waitForInteractions();
invariant(
rootNavigatorContext,
'RootNavigatorContext should be set in onScreenBlur',
);
rootNavigatorContext.setKeyboardHandlingEnabled(true);
}),
[navigation, rootNavigatorContext],
);
const index = useSelector(state => threadSearchIndex(state));
const onScreenThreadInfos = useSelector(state =>
onScreenEntryEditableThreadInfos(state),
);
return (
);
}
const styles = StyleSheet.create({
threadListItem: {
paddingLeft: 10,
paddingRight: 10,
paddingVertical: 2,
},
});
export default ThreadPickerModal;
diff --git a/native/chat/new-messages-pill.react.js b/native/chat/new-messages-pill.react.js
index d97f0afc5..ef3f7cd51 100644
--- a/native/chat/new-messages-pill.react.js
+++ b/native/chat/new-messages-pill.react.js
@@ -1,73 +1,73 @@
// @flow
import * as React from 'react';
import { TouchableOpacity, View, Text, Platform, Animated } from 'react-native';
import Icon from 'react-native-vector-icons/FontAwesome';
import { useStyles } from '../themes/colors';
import type { ViewStyle } from '../types/styles';
type Props = {
- onPress: () => mixed,
- newMessageCount: number,
- containerStyle?: ViewStyle,
- style?: ViewStyle,
+ +onPress: () => mixed,
+ +newMessageCount: number,
+ +containerStyle?: ViewStyle,
+ +style?: ViewStyle,
...React.ElementConfig,
};
function NewMessagesPill(props: Props): React.Node {
const {
onPress,
newMessageCount,
containerStyle,
style,
...containerProps
} = props;
const styles = useStyles(unboundStyles);
return (
{newMessageCount}
);
}
const unboundStyles = {
countBubble: {
alignItems: 'center',
backgroundColor: 'vibrantGreenButton',
borderRadius: 25,
height: 25,
justifyContent: 'center',
paddingBottom: Platform.OS === 'android' ? 2 : 0,
paddingLeft: 1,
position: 'absolute',
right: -8,
top: -8,
width: 25,
},
countText: {
color: 'white',
textAlign: 'center',
},
button: {
backgroundColor: 'floatingButtonBackground',
borderColor: 'floatingButtonLabel',
borderRadius: 30,
borderWidth: 4,
paddingHorizontal: 12,
paddingVertical: 6,
},
icon: {
color: 'floatingButtonLabel',
fontSize: 32,
fontWeight: 'bold',
},
};
export default NewMessagesPill;
diff --git a/native/components/clearable-text-input.react.ios.js b/native/components/clearable-text-input.react.ios.js
index 085c85eeb..ac127a6bd 100644
--- a/native/components/clearable-text-input.react.ios.js
+++ b/native/components/clearable-text-input.react.ios.js
@@ -1,189 +1,189 @@
// @flow
import invariant from 'invariant';
import * as React from 'react';
import { TextInput as BaseTextInput, View, StyleSheet } from 'react-native';
import type { KeyPressEvent } from '../types/react-native';
import type { ClearableTextInputProps } from './clearable-text-input';
import TextInput from './text-input.react';
type State = {
- textInputKey: number,
+ +textInputKey: number,
};
class ClearableTextInput extends React.PureComponent<
ClearableTextInputProps,
State,
> {
state: State = {
textInputKey: 0,
};
pendingMessage: ?{ value: string, resolve: (value: string) => void };
lastKeyPressed: ?string;
lastTextInputSent: number = -1;
currentTextInput: ?React.ElementRef;
focused: boolean = false;
sendMessage() {
if (this.pendingMessageSent) {
return;
}
const { pendingMessage } = this;
invariant(pendingMessage, 'cannot send an empty message');
pendingMessage.resolve(pendingMessage.value);
const textInputSent = this.state.textInputKey - 1;
if (textInputSent > this.lastTextInputSent) {
this.lastTextInputSent = textInputSent;
}
}
get pendingMessageSent(): boolean {
return this.lastTextInputSent >= this.state.textInputKey - 1;
}
onOldInputChangeText: (text: string) => void = text => {
const { pendingMessage, lastKeyPressed } = this;
invariant(
pendingMessage,
'onOldInputChangeText should have a pendingMessage',
);
if (
!this.pendingMessageSent &&
lastKeyPressed &&
lastKeyPressed.length > 1
) {
// This represents an autocorrect event on blur
pendingMessage.value = text;
}
this.lastKeyPressed = null;
this.sendMessage();
this.updateTextFromOldInput(text);
};
updateTextFromOldInput(text: string) {
const { pendingMessage } = this;
invariant(
pendingMessage,
'updateTextFromOldInput should have a pendingMessage',
);
const pendingValue = pendingMessage.value;
if (!pendingValue || !text.startsWith(pendingValue)) {
return;
}
const newValue = text.substring(pendingValue.length);
if (this.props.value === newValue) {
return;
}
this.props.onChangeText(newValue);
}
onOldInputKeyPress: (event: KeyPressEvent) => void = event => {
const { key } = event.nativeEvent;
if (this.lastKeyPressed && this.lastKeyPressed.length > key.length) {
return;
}
this.lastKeyPressed = key;
this.props.onKeyPress && this.props.onKeyPress(event);
};
onOldInputBlur: () => void = () => {
this.sendMessage();
};
onOldInputFocus: () => void = () => {
// It's possible for the user to press the old input after the new one
// appears. We can prevent that with pointerEvents="none", but that causes a
// blur event when we set it, which makes the keyboard briefly pop down
// before popping back up again when textInputRef is called below. Instead
// we try to catch the focus event here and refocus the currentTextInput
if (this.currentTextInput) {
this.currentTextInput.focus();
}
};
textInputRef: (
textInput: ?React.ElementRef,
) => void = textInput => {
if (this.focused && textInput) {
textInput.focus();
}
this.currentTextInput = textInput;
this.props.textInputRef(textInput);
};
async getValueAndReset(): Promise {
const { value } = this.props;
this.props.onChangeText('');
if (!this.focused) {
return value;
}
return await new Promise(resolve => {
this.pendingMessage = { value, resolve };
this.setState(prevState => ({
textInputKey: prevState.textInputKey + 1,
}));
});
}
onFocus: () => void = () => {
this.focused = true;
};
onBlur: () => void = () => {
this.focused = false;
if (this.pendingMessage) {
// This is to catch a race condition where somebody hits the send button
// and then blurs the TextInput before the textInputKey increment can
// rerender this component. With this.focused set to false, the new
// TextInput won't focus, and the old TextInput won't blur, which means
// nothing will call sendMessage unless we do it right here.
this.sendMessage();
}
};
render(): React.Node {
const { textInputRef, ...props } = this.props;
const textInputs = [];
if (this.state.textInputKey > 0) {
textInputs.push(
,
);
}
textInputs.push(
,
);
return {textInputs};
}
}
const styles = StyleSheet.create({
invisibleTextInput: {
opacity: 0,
position: 'absolute',
},
textInputContainer: {
flex: 1,
},
});
export default ClearableTextInput;
diff --git a/native/components/content-loading.react.js b/native/components/content-loading.react.js
index 6c8faa296..9bdeb16ae 100644
--- a/native/components/content-loading.react.js
+++ b/native/components/content-loading.react.js
@@ -1,39 +1,39 @@
// @flow
import * as React from 'react';
import { View, ActivityIndicator, StyleSheet } from 'react-native';
import type { Colors } from '../themes/colors';
type Props = {
- fillType: 'flex' | 'absolute',
- colors: Colors,
+ +fillType: 'flex' | 'absolute',
+ +colors: Colors,
};
function ContentLoading(props: Props): React.Node {
const viewStyle =
props.fillType === 'flex' ? styles.fullFlex : styles.absoluteContainer;
return (
);
}
const styles = StyleSheet.create({
absoluteContainer: {
bottom: 0,
left: 0,
position: 'absolute',
right: 0,
top: 0,
},
fullFlex: {
flex: 1,
},
});
export default ContentLoading;
diff --git a/native/components/single-line.react.js b/native/components/single-line.react.js
index c71be5ef7..6bfbb21ae 100644
--- a/native/components/single-line.react.js
+++ b/native/components/single-line.react.js
@@ -1,21 +1,21 @@
// @flow
import * as React from 'react';
import { Text } from 'react-native';
import { firstLine } from 'lib/utils/string-utils';
type Props = {
...React.ElementConfig,
- children: ?string,
+ +children: ?string,
};
function SingleLine(props: Props): React.Node {
const text = firstLine(props.children);
return (
{text}
);
}
export { SingleLine };
diff --git a/native/error-boundary.react.js b/native/error-boundary.react.js
index c6e46e0d9..435c418ed 100644
--- a/native/error-boundary.react.js
+++ b/native/error-boundary.react.js
@@ -1,57 +1,57 @@
// @flow
import * as React from 'react';
import type { ErrorInfo, ErrorData } from 'lib/types/report-types';
import Crash from './crash.react';
let instance: ?ErrorBoundary = null;
const defaultHandler = global.ErrorUtils.getGlobalHandler();
global.ErrorUtils.setGlobalHandler(error => {
defaultHandler(error);
if (instance) {
instance.reportError(error);
}
});
type Props = {
- children: React.Node,
+ +children: React.Node,
};
type State = {
- errorData: $ReadOnlyArray,
+ +errorData: $ReadOnlyArray,
};
class ErrorBoundary extends React.PureComponent {
state: State = {
errorData: [],
};
componentDidMount() {
instance = this;
}
componentWillUnmount() {
instance = null;
}
componentDidCatch(error: Error, info: ErrorInfo) {
this.setState(prevState => ({
errorData: [...prevState.errorData, { error, info }],
}));
}
reportError(error: Error) {
this.setState(prevState => ({
errorData: [...prevState.errorData, { error }],
}));
}
render(): React.Node {
if (this.state.errorData.length > 0) {
return ;
}
return this.props.children;
}
}
export default ErrorBoundary;
diff --git a/native/media/send-media-button.react.js b/native/media/send-media-button.react.js
index 738f17974..dbee0ca8c 100644
--- a/native/media/send-media-button.react.js
+++ b/native/media/send-media-button.react.js
@@ -1,87 +1,87 @@
// @flow
import * as React from 'react';
import {
TouchableOpacity,
View,
Text,
StyleSheet,
Platform,
Animated,
} from 'react-native';
import Icon from 'react-native-vector-icons/FontAwesome';
import type { ViewStyle } from '../types/styles';
type Props = {
...React.ElementConfig,
- onPress: () => mixed,
- queueCount?: number,
- containerStyle?: ViewStyle,
- style?: ViewStyle,
+ +onPress: () => mixed,
+ +queueCount?: number,
+ +containerStyle?: ViewStyle,
+ +style?: ViewStyle,
};
function SendMediaButton(props: Props): React.Node {
const {
onPress,
queueCount,
containerStyle,
style,
...containerProps
} = props;
let queueCountText = null;
if (queueCount !== undefined && queueCount !== null) {
queueCountText = (
{queueCount}
);
}
return (
{queueCountText}
);
}
const styles = StyleSheet.create({
queueCountBubble: {
alignItems: 'center',
backgroundColor: '#222222',
borderRadius: 25,
height: 25,
justifyContent: 'center',
paddingBottom: Platform.OS === 'android' ? 2 : 0,
paddingLeft: 1,
position: 'absolute',
right: -8,
top: -8,
width: 25,
},
queueCountText: {
color: 'white',
textAlign: 'center',
},
sendButton: {
backgroundColor: '#7ED321',
borderColor: 'white',
borderRadius: 30,
borderWidth: 4,
paddingBottom: 16,
paddingLeft: 14,
paddingRight: 16,
paddingTop: 14,
},
sendIcon: {
color: 'white',
fontSize: 22,
},
});
export default SendMediaButton;
diff --git a/native/navigation/action-result-modal.react.js b/native/navigation/action-result-modal.react.js
index 4c83a1291..cb96a9efd 100644
--- a/native/navigation/action-result-modal.react.js
+++ b/native/navigation/action-result-modal.react.js
@@ -1,80 +1,80 @@
// @flow
import invariant from 'invariant';
import * as React from 'react';
import { View, Text } from 'react-native';
import Animated from 'react-native-reanimated';
import { useSelector } from '../redux/redux-utils';
import { useOverlayStyles } from '../themes/colors';
import type { AppNavigationProp } from './app-navigator.react';
import { OverlayContext } from './overlay-context';
import type { NavigationRoute } from './route-names';
export type ActionResultModalParams = {
message: string,
preventPresses: true,
};
type Props = {
- navigation: AppNavigationProp<'ActionResultModal'>,
- route: NavigationRoute<'ActionResultModal'>,
+ +navigation: AppNavigationProp<'ActionResultModal'>,
+ +route: NavigationRoute<'ActionResultModal'>,
};
function ActionResultModal(props: Props): React.Node {
const overlayContext = React.useContext(OverlayContext);
invariant(overlayContext, 'ActionResultModal should have OverlayContext');
const { position } = overlayContext;
// Timer resets whenever message updates
const { goBackOnce } = props.navigation;
const { message } = props.route.params;
React.useEffect(() => {
const timeoutID = setTimeout(goBackOnce, 2000);
return () => clearTimeout(timeoutID);
}, [message, goBackOnce]);
const styles = useOverlayStyles(ourStyles);
const bottomInset = useSelector(state => state.dimensions.bottomInset);
const containerStyle = {
...styles.container,
opacity: position,
paddingBottom: bottomInset + 100,
};
return (
{message}
);
}
const ourStyles = {
backdrop: {
backgroundColor: 'modalContrastBackground',
bottom: 0,
left: 0,
opacity: 'modalContrastOpacity',
position: 'absolute',
right: 0,
top: 0,
},
container: {
alignItems: 'center',
flex: 1,
justifyContent: 'flex-end',
},
message: {
borderRadius: 10,
overflow: 'hidden',
padding: 10,
},
text: {
color: 'modalContrastForegroundLabel',
fontSize: 20,
textAlign: 'center',
},
};
export default ActionResultModal;
diff --git a/native/navigation/header.react.js b/native/navigation/header.react.js
index e65c0919d..fcf96a314 100644
--- a/native/navigation/header.react.js
+++ b/native/navigation/header.react.js
@@ -1,20 +1,20 @@
// @flow
import { Header, type StackHeaderProps } from '@react-navigation/stack';
import * as React from 'react';
import DisconnectedBar from './disconnected-bar.react';
type Props = {
...StackHeaderProps,
- activeTab: boolean,
+ +activeTab: boolean,
};
export default function CustomHeader(props: Props): React.Node {
const { activeTab, ...rest } = props;
return (
<>
>
);
}
diff --git a/native/navigation/modal-pruner.react.js b/native/navigation/modal-pruner.react.js
index 33246e392..f79504ffd 100644
--- a/native/navigation/modal-pruner.react.js
+++ b/native/navigation/modal-pruner.react.js
@@ -1,135 +1,135 @@
// @flow
import type {
PossiblyStaleNavigationState,
PossiblyStaleRoute,
} from '@react-navigation/native';
import invariant from 'invariant';
import * as React from 'react';
import {
clearRootModalsActionType,
clearOverlayModalsActionType,
} from './action-types';
import type { NavContextType } from './navigation-context';
import { AppRouteName } from './route-names';
type DependencyInfo = {
status: 'missing' | 'resolved' | 'unresolved',
presenter: ?string,
presenting: string[],
parentRouteName: ?string,
};
function collectDependencyInfo(
route: PossiblyStaleNavigationState | PossiblyStaleRoute<>,
dependencyMap?: Map = new Map(),
parentRouteName?: ?string,
): Map {
let state, routeName;
if (route.name === undefined) {
state = route;
} else if (route.state) {
({ state, name: routeName } = route);
}
if (state) {
for (const child of state.routes) {
collectDependencyInfo(child, dependencyMap, routeName);
}
return dependencyMap;
}
if (!route.key) {
return dependencyMap;
}
const { key } = route;
const presenter =
route.params && route.params.presentedFrom
? route.params.presentedFrom
: null;
invariant(
presenter === null || typeof presenter === 'string',
'presentedFrom should be a string',
);
let status = 'resolved';
if (presenter) {
const presenterInfo = dependencyMap.get(presenter);
if (!presenterInfo) {
status = 'unresolved';
dependencyMap.set(presenter, {
status: 'missing',
presenter: undefined,
presenting: [key],
parentRouteName: undefined,
});
} else if (presenterInfo) {
status = presenterInfo.status;
presenterInfo.presenting.push(key);
}
}
const existingInfo = dependencyMap.get(key);
const presenting = existingInfo ? existingInfo.presenting : [];
dependencyMap.set(key, {
status,
presenter,
presenting,
parentRouteName,
});
if (status === 'resolved') {
const toResolve = [...presenting];
while (toResolve.length > 0) {
const presentee = toResolve.pop();
const dependencyInfo = dependencyMap.get(presentee);
invariant(dependencyInfo, 'could not find presentee');
dependencyInfo.status = 'resolved';
toResolve.push(...dependencyInfo.presenting);
}
}
return dependencyMap;
}
type Props = {
- navContext: NavContextType,
+ +navContext: NavContextType,
};
function ModalPruner(props: Props): null {
const { state, dispatch } = props.navContext;
const [pruneRootModals, pruneOverlayModals] = React.useMemo(() => {
const dependencyMap = collectDependencyInfo(state);
const rootModals = [],
overlayModals = [];
for (const [key, info] of dependencyMap) {
if (info.status !== 'unresolved') {
continue;
}
if (!info.parentRouteName) {
rootModals.push(key);
} else if (info.parentRouteName === AppRouteName) {
overlayModals.push(key);
}
}
return [rootModals, overlayModals];
}, [state]);
React.useEffect(() => {
if (pruneRootModals.length > 0) {
dispatch({
type: (clearRootModalsActionType: 'CLEAR_ROOT_MODALS'),
payload: { keys: pruneRootModals },
});
}
if (pruneOverlayModals.length > 0) {
dispatch({
type: (clearOverlayModalsActionType: 'CLEAR_OVERLAY_MODALS'),
payload: { keys: pruneOverlayModals },
});
}
}, [dispatch, pruneRootModals, pruneOverlayModals]);
return null;
}
export default ModalPruner;
diff --git a/native/profile/custom-server-modal.react.js b/native/profile/custom-server-modal.react.js
index 47875f431..8771f297f 100644
--- a/native/profile/custom-server-modal.react.js
+++ b/native/profile/custom-server-modal.react.js
@@ -1,137 +1,137 @@
// @flow
import * as React from 'react';
import { Text } from 'react-native';
import { useDispatch } from 'react-redux';
import type { Dispatch } from 'lib/types/redux-types';
import { setURLPrefix } from 'lib/utils/url-utils';
import Button from '../components/button.react';
import Modal from '../components/modal.react';
import TextInput from '../components/text-input.react';
import type { RootNavigationProp } from '../navigation/root-navigator.react';
import type { NavigationRoute } from '../navigation/route-names';
import { useSelector } from '../redux/redux-utils';
import { useStyles } from '../themes/colors';
import { setCustomServer } from '../utils/url-utils';
export type CustomServerModalParams = {
presentedFrom: string,
};
type BaseProps = {
+navigation: RootNavigationProp<'CustomServerModal'>,
+route: NavigationRoute<'CustomServerModal'>,
};
type Props = {
...BaseProps,
+urlPrefix: string,
+customServer: ?string,
+styles: typeof unboundStyles,
+dispatch: Dispatch,
};
type State = {
- customServer: string,
+ +customServer: string,
};
class CustomServerModal extends React.PureComponent {
constructor(props: Props) {
super(props);
const { customServer } = props;
this.state = {
customServer: customServer ? customServer : '',
};
}
render() {
return (
);
}
onChangeCustomServer = (newCustomServer: string) => {
this.setState({ customServer: newCustomServer });
};
onPressGo = () => {
const { customServer } = this.state;
if (customServer !== this.props.urlPrefix) {
this.props.dispatch({
type: setURLPrefix,
payload: customServer,
});
}
if (customServer && customServer !== this.props.customServer) {
this.props.dispatch({
type: setCustomServer,
payload: customServer,
});
}
this.props.navigation.goBackOnce();
};
}
const unboundStyles = {
button: {
backgroundColor: 'greenButton',
borderRadius: 5,
marginHorizontal: 2,
marginVertical: 2,
paddingHorizontal: 12,
paddingVertical: 4,
},
buttonText: {
color: 'white',
fontSize: 18,
textAlign: 'center',
},
container: {
justifyContent: 'flex-end',
},
modal: {
flex: 0,
flexDirection: 'row',
},
textInput: {
color: 'modalBackgroundLabel',
flex: 1,
fontSize: 16,
margin: 0,
padding: 0,
borderBottomColor: 'transparent',
},
};
const ConnectedCustomServerModal: React.ComponentType = React.memo(
function ConnectedCustomServerModal(props: BaseProps) {
const urlPrefix = useSelector(state => state.urlPrefix);
const customServer = useSelector(state => state.customServer);
const styles = useStyles(unboundStyles);
const dispatch = useDispatch();
return (
);
},
);
export default ConnectedCustomServerModal;
diff --git a/native/push/in-app-notif.react.js b/native/push/in-app-notif.react.js
index 4d7bf8d25..2046de329 100644
--- a/native/push/in-app-notif.react.js
+++ b/native/push/in-app-notif.react.js
@@ -1,87 +1,87 @@
// @flow
import * as React from 'react';
import { View, Text, StyleSheet, Platform } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { SingleLine } from '../components/single-line.react';
import type { GlobalTheme } from '../types/themes';
const edges = ['top'];
type Props = {
- title: ?string,
- message: string,
- activeTheme: ?GlobalTheme,
+ +title: ?string,
+ +message: string,
+ +activeTheme: ?GlobalTheme,
};
function InAppNotif(props: Props): React.Node {
const useLightStyle = Platform.OS === 'ios' && props.activeTheme !== 'dark';
let title = null;
if (props.title) {
const titleStyles = [
styles.title,
useLightStyle ? styles.lightTitle : null,
];
title = (
<>
{props.title}
{'\n'}
>
);
}
const textStyles = [styles.text, useLightStyle ? styles.lightText : null];
const notificationContent = (
{title}
{props.message}
);
if (Platform.OS === 'android') {
return (
{notificationContent}
);
}
return {notificationContent};
}
const styles = StyleSheet.create({
lightText: {
color: 'white',
},
lightTitle: {
color: 'white',
},
notif: {
alignItems: 'flex-start',
alignSelf: 'flex-start',
justifyContent: 'flex-start',
width: '100%',
},
text: {
...Platform.select({
ios: {
fontSize: 16,
marginTop: 16,
marginBottom: 6,
color: 'black',
},
default: {
fontSize: 18,
marginVertical: 16,
},
}),
marginHorizontal: 10,
},
title: {
color: 'black',
fontWeight: 'bold',
},
});
export default InAppNotif;
diff --git a/web/calendar/calendar.react.js b/web/calendar/calendar.react.js
index 38343dea8..52fdcc144 100644
--- a/web/calendar/calendar.react.js
+++ b/web/calendar/calendar.react.js
@@ -1,288 +1,288 @@
// @flow
import { faFilter } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import dateFormat from 'dateformat';
import invariant from 'invariant';
import * as React from 'react';
import {
updateCalendarQueryActionTypes,
updateCalendarQuery,
} from 'lib/actions/entry-actions';
import { currentDaysToEntries } from 'lib/selectors/thread-selectors';
import { isLoggedIn } from 'lib/selectors/user-selectors';
import {
type EntryInfo,
type CalendarQuery,
type CalendarQueryUpdateResult,
type CalendarQueryUpdateStartingPayload,
} from 'lib/types/entry-types';
import {
type DispatchActionPromise,
useDispatchActionPromise,
useServerCall,
} from 'lib/utils/action-utils';
import {
getDate,
dateString,
startDateForYearAndMonth,
endDateForYearAndMonth,
} from 'lib/utils/date-utils';
import { useSelector } from '../redux/redux-utils';
import {
yearAssertingSelector,
monthAssertingSelector,
webCalendarQuery,
} from '../selectors/nav-selectors';
import SWMansionIcon from '../SWMansionIcon.react';
import type { NavInfo } from '../types/nav-types';
import { canonicalURLFromReduxState } from '../url-utils';
import css from './calendar.css';
import Day from './day.react';
import FilterPanel from './filter-panel.react';
type BaseProps = {
+url: string,
};
type Props = {
...BaseProps,
+year: number,
+month: number,
+daysToEntries: { +[dayString: string]: EntryInfo[] },
+navInfo: NavInfo,
+currentCalendarQuery: () => CalendarQuery,
+loggedIn: boolean,
+dispatchActionPromise: DispatchActionPromise,
+updateCalendarQuery: (
calendarQuery: CalendarQuery,
reduxAlreadyUpdated?: boolean,
) => Promise,
};
type State = {
- filterPanelOpen: boolean,
+ +filterPanelOpen: boolean,
};
class Calendar extends React.PureComponent {
state: State = {
filterPanelOpen: false,
};
getDate(
dayOfMonth: number,
monthInput: ?number = undefined,
yearInput: ?number = undefined,
) {
return getDate(
yearInput ? yearInput : this.props.year,
monthInput ? monthInput : this.props.month,
dayOfMonth,
);
}
prevMonthDates() {
const { year, month } = this.props;
const lastMonthDate = getDate(year, month - 1, 1);
const prevYear = lastMonthDate.getFullYear();
const prevMonth = lastMonthDate.getMonth() + 1;
return {
startDate: startDateForYearAndMonth(prevYear, prevMonth),
endDate: endDateForYearAndMonth(prevYear, prevMonth),
};
}
nextMonthDates() {
const { year, month } = this.props;
const nextMonthDate = getDate(year, month + 1, 1);
const nextYear = nextMonthDate.getFullYear();
const nextMonth = nextMonthDate.getMonth() + 1;
return {
startDate: startDateForYearAndMonth(nextYear, nextMonth),
endDate: endDateForYearAndMonth(nextYear, nextMonth),
};
}
render() {
const { year, month } = this.props;
const monthName = dateFormat(getDate(year, month, 1), 'mmmm');
const prevURL = canonicalURLFromReduxState(
{ ...this.props.navInfo, ...this.prevMonthDates() },
this.props.url,
this.props.loggedIn,
);
const nextURL = canonicalURLFromReduxState(
{ ...this.props.navInfo, ...this.nextMonthDates() },
this.props.url,
this.props.loggedIn,
);
const lastDayOfMonth = this.getDate(0, this.props.month + 1);
const totalDaysInMonth = lastDayOfMonth.getDate();
const firstDayToPrint = 1 - this.getDate(1).getDay();
const lastDayToPrint = totalDaysInMonth + 6 - lastDayOfMonth.getDay();
const rows = [];
let columns = [];
let week = 1;
let tabIndex = 1;
for (
let curDayOfMonth = firstDayToPrint;
curDayOfMonth <= lastDayToPrint;
curDayOfMonth++
) {
if (curDayOfMonth < 1 || curDayOfMonth > totalDaysInMonth) {
columns.push(
);
} else {
const dayString = dateString(
this.props.year,
this.props.month,
curDayOfMonth,
);
const entries = this.props.daysToEntries[dayString];
invariant(
entries,
'the currentDaysToEntries selector should make sure all dayStrings ' +
`in the current range have entries, but ${dayString} did not`,
);
columns.push(
,
);
tabIndex += entries.length;
}
if (columns.length === 7) {
rows.push(
{columns}
);
columns = [];
}
}
let filterPanel = null;
let calendarContentStyle = null;
let filterButtonStyle = null;
if (this.state.filterPanelOpen) {
filterPanel = ;
calendarContentStyle = { marginLeft: '300px' };
filterButtonStyle = { backgroundColor: 'rgba(0,0,0,0.67)' };
}
return (