diff --git a/native/account/terms-and-privacy-modal.react.js b/native/account/terms-and-privacy-modal.react.js
new file mode 100644
--- /dev/null
+++ b/native/account/terms-and-privacy-modal.react.js
@@ -0,0 +1,190 @@
+// @flow
+
+import { useIsFocused } from '@react-navigation/native';
+import * as React from 'react';
+import {
+ ActivityIndicator,
+ BackHandler,
+ Linking,
+ Platform,
+ Text,
+ View,
+} from 'react-native';
+
+import {
+ policyAcknowledgment,
+ policyAcknowledgmentActionTypes,
+} from 'lib/actions/user-actions';
+import { type PolicyType, policyTypes } from 'lib/facts/policies';
+import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors';
+import {
+ useDispatchActionPromise,
+ useServerCall,
+} from 'lib/utils/action-utils';
+import { acknowledgePolicy } from 'lib/utils/policy-acknowledge-utlis';
+
+import Button from '../components/button.react';
+import Modal from '../components/modal.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';
+
+export type TermsAndPrivacyModalParams = {
+ +policyType: PolicyType,
+};
+
+type Props = {
+ +navigation: RootNavigationProp<'TermsAndPrivacyModal'>,
+ +route: NavigationRoute<'TermsAndPrivacyModal'>,
+};
+
+const loadingStatusSelector = createLoadingStatusSelector(
+ policyAcknowledgmentActionTypes,
+);
+
+function TermsAndPrivacyModal(props: Props): React.Node {
+ const loadingStatus = useSelector(loadingStatusSelector);
+ const [acknowledgmentError, setAcknowledgmentError] = React.useState('');
+ const sendAcknowledgmentRequest = useServerCall(policyAcknowledgment);
+ const dispatchActionPromise = useDispatchActionPromise();
+
+ const policyType = props.route.params.policyType;
+ const policyState = useSelector(store => store.userPolicies[policyType]);
+ const isAcknowledged = policyState?.isAcknowledged;
+ const isFocused = useIsFocused();
+
+ React.useEffect(() => {
+ if (isAcknowledged && isFocused) {
+ props.navigation.goBack();
+ }
+ }, [isAcknowledged, props.navigation, isFocused]);
+
+ const onAccept = React.useCallback(() => {
+ acknowledgePolicy(
+ policyTypes.tosAndPrivacyPolicy,
+ dispatchActionPromise,
+ sendAcknowledgmentRequest,
+ setAcknowledgmentError,
+ );
+ }, [dispatchActionPromise, sendAcknowledgmentRequest]);
+
+ const styles = useStyles(unboundStyles);
+
+ const buttonContent = React.useMemo(() => {
+ if (loadingStatus === 'loading') {
+ return (
+
+
+
+ );
+ }
+ return I accept;
+ }, [loadingStatus, styles.buttonText, styles.loading]);
+
+ const onBackPress = props.navigation.isFocused;
+ React.useEffect(() => {
+ BackHandler.addEventListener('hardwareBackPress', onBackPress);
+ return () => {
+ BackHandler.removeEventListener('hardwareBackPress', onBackPress);
+ };
+ }, [onBackPress]);
+
+ const safeAreaEdges = ['top', 'bottom'];
+ return (
+
+ Terms of Service and Privacy Policy
+
+ We recently updated our
+
+ Terms of Service
+
+ &
+
+ Privacy Policy
+
+
+ . In order to continue using Comm, we're asking you to read
+ through, acknowledge, and accept the updated policies.
+
+
+
+
+
+ {acknowledgmentError}
+
+
+ );
+}
+
+const unboundStyles = {
+ modal: {
+ backgroundColor: 'modalForeground',
+ paddingBottom: 10,
+ paddingTop: 32,
+ paddingHorizontal: 32,
+ flex: 0,
+ borderColor: 'modalForegroundBorder',
+ },
+ header: {
+ color: 'modalForegroundLabel',
+ fontSize: 20,
+ fontWeight: '600',
+ textAlign: 'center',
+ paddingBottom: 16,
+ },
+ label: {
+ color: 'modalForegroundSecondaryLabel',
+ fontSize: 16,
+ lineHeight: 20,
+ textAlign: 'center',
+ },
+ link: {
+ color: 'purpleLink',
+ fontWeight: 'bold',
+ },
+ buttonsContainer: {
+ flexDirection: 'column',
+ marginTop: 24,
+ height: 72,
+ paddingHorizontal: 16,
+ },
+ button: {
+ borderRadius: 5,
+ height: 48,
+ justifyContent: 'center',
+ alignItems: 'center',
+ backgroundColor: 'purpleButton',
+ },
+ buttonText: {
+ color: 'white',
+ fontSize: 16,
+ fontWeight: '600',
+ textAlign: 'center',
+ },
+ error: {
+ marginTop: 6,
+ fontStyle: 'italic',
+ color: 'redText',
+ textAlign: 'center',
+ },
+ loading: {
+ paddingTop: Platform.OS === 'android' ? 0 : 6,
+ },
+};
+
+const onTermsOfUsePressed = () => {
+ Linking.openURL('https://comm.app/terms');
+};
+
+const onPrivacyPolicyPressed = () => {
+ Linking.openURL('https://comm.app/privacy');
+};
+
+export default TermsAndPrivacyModal;
diff --git a/native/components/modal.react.js b/native/components/modal.react.js
--- a/native/components/modal.react.js
+++ b/native/components/modal.react.js
@@ -14,14 +14,18 @@
+containerStyle?: ViewStyle,
+modalStyle?: ViewStyle,
+safeAreaEdges?: $ReadOnlyArray<'top' | 'right' | 'bottom' | 'left'>,
+ +disableClosing?: boolean,
}>;
function Modal(props: Props): React.Node {
const navigation = useNavigation();
const close = React.useCallback(() => {
+ if (props.disableClosing) {
+ return;
+ }
if (navigation.isFocused()) {
navigation.goBack();
}
- }, [navigation]);
+ }, [navigation, props.disableClosing]);
const styles = useStyles(unboundStyles);
const { containerStyle, modalStyle, children, safeAreaEdges } = props;
diff --git a/native/navigation/root-navigator.react.js b/native/navigation/root-navigator.react.js
--- a/native/navigation/root-navigator.react.js
+++ b/native/navigation/root-navigator.react.js
@@ -18,6 +18,7 @@
import { enableScreens } from 'react-native-screens';
import LoggedOutModal from '../account/logged-out-modal.react';
+import TermsAndPrivacyModal from '../account/terms-and-privacy-modal.react';
import ThreadPickerModal from '../calendar/thread-picker-modal.react';
import ImagePasteModal from '../chat/image-paste-modal.react';
import AddUsersModal from '../chat/settings/add-users-modal.react';
@@ -43,6 +44,7 @@
SidebarListModalRouteName,
type ScreenParamList,
type RootParamList,
+ TermsAndPrivacyRouteName,
} from './route-names';
enableScreens();
@@ -151,6 +153,10 @@
const modalOverlayScreenOptions = {
cardOverlayEnabled: true,
};
+const termsAndPrivacyModalScreenOptions = {
+ gestureEnabled: false,
+ cardOverlayEnabled: true,
+};
export type RootRouterNavigationProp<
ParamList: ParamListBase = ParamListBase,
@@ -181,6 +187,11 @@
options={disableGesturesScreenOptions}
/>
+