Page MenuHomePhorge

D5831.1768197756.diff
No OneTemporary

Size
9 KB
Referenced Files
None
Subscribers
None

D5831.1768197756.diff

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,193 @@
+// @flow
+
+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 loading = 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]);
+
+ React.useEffect(() => {
+ if (policyState?.isAcknowledged) {
+ props.navigation.goBack();
+ }
+ }, [policyState, policyType, props.navigation]);
+
+ const onAccept = React.useCallback(() => {
+ acknowledgePolicy(
+ policyTypes.tosAndPrivacyPolicy,
+ dispatchActionPromise,
+ sendAcknowledgmentRequest,
+ e => setAcknowledgmentError(e),
+ );
+ }, [dispatchActionPromise, sendAcknowledgmentRequest]);
+
+ const styles = useStyles(unboundStyles);
+
+ const buttonContent = React.useMemo(() => {
+ if (loading === 'loading') {
+ return (
+ <View style={styles.loading}>
+ <ActivityIndicator size="small" color="#D3D3D3" />
+ </View>
+ );
+ }
+ return <Text style={styles.buttonText}>I accept</Text>;
+ }, [loading, styles.buttonText, styles.loading]);
+
+ const onBackPress = React.useCallback(() => {
+ if (!props.navigation.isFocused()) {
+ return false;
+ }
+ return true;
+ }, [props.navigation]);
+
+ React.useEffect(() => {
+ BackHandler.addEventListener('hardwareBackPress', onBackPress);
+ return () => {
+ BackHandler.removeEventListener('hardwareBackPress', onBackPress);
+ };
+ }, [onBackPress]);
+
+ const safeAreaEdges = ['top', 'bottom'];
+ return (
+ <Modal
+ disableClosing={true}
+ modalStyle={styles.modal}
+ safeAreaEdges={safeAreaEdges}
+ >
+ <Text style={styles.header}>Terms of Service and Privacy Policy</Text>
+ <Text style={styles.label}>
+ <Text>We recently updated our&nbsp;</Text>
+ <Text style={styles.link} onPress={onTermsOfUsePressed}>
+ Terms of Service
+ </Text>
+ <Text>&nbsp;&amp;&nbsp;</Text>
+ <Text style={styles.link} onPress={onPrivacyPolicyPressed}>
+ Privacy Policy
+ </Text>
+ <Text>
+ . We&apos;re asking you accept those to make sure you have a chance to
+ acknowledge the updated policies.
+ </Text>
+ </Text>
+
+ <View style={styles.buttonsContainer}>
+ <Button style={styles.button} onPress={onAccept}>
+ <Text style={styles.buttonText}>{buttonContent}</Text>
+ </Button>
+ <Text style={styles.error}>{acknowledgmentError}</Text>
+ </View>
+ </Modal>
+ );
+}
+
+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}
/>
<Root.Screen name={AppRouteName} component={AppNavigator} />
+ <Root.Screen
+ name={TermsAndPrivacyRouteName}
+ component={TermsAndPrivacyModal}
+ options={termsAndPrivacyModalScreenOptions}
+ />
<Root.Screen
name={ThreadPickerModalRouteName}
component={ThreadPickerModal}
diff --git a/native/navigation/route-names.js b/native/navigation/route-names.js
--- a/native/navigation/route-names.js
+++ b/native/navigation/route-names.js
@@ -2,6 +2,7 @@
import type { RouteProp } from '@react-navigation/native';
+import type { TermsAndPrivacyModalParams } from '../account/terms-and-privacy-modal.react';
import type { ThreadPickerModalParams } from '../calendar/thread-picker-modal.react';
import type { ComposeSubchannelParams } from '../chat/compose-subchannel.react';
import type { ImagePasteModalParams } from '../chat/image-paste-modal.react';
@@ -67,6 +68,7 @@
'ThreadSettingsMemberTooltipModal';
export const ThreadSettingsRouteName = 'ThreadSettings';
export const VideoPlaybackModalRouteName = 'VideoPlaybackModal';
+export const TermsAndPrivacyRouteName = 'TermsAndPrivacyModal';
export type RootParamList = {
+LoggedOutModal: void,
@@ -78,6 +80,7 @@
+ComposeSubchannelModal: ComposeSubchannelModalParams,
+SidebarListModal: SidebarListModalParams,
+ImagePasteModal: ImagePasteModalParams,
+ +TermsAndPrivacyModal: TermsAndPrivacyModalParams,
};
export type MessageTooltipRouteNames =
diff --git a/native/themes/colors.js b/native/themes/colors.js
--- a/native/themes/colors.js
+++ b/native/themes/colors.js
@@ -73,6 +73,8 @@
panelIosHighlightUnderlay: '#EEEEEEDD',
panelSecondaryForeground: '#F5F5F5',
panelSecondaryForegroundBorder: '#D1D1D6',
+ purpleLink: '#7E57C2',
+ purpleButton: '#7E57C2',
redButton: '#BB8888',
redText: '#FF4444',
spoiler: '#33332C',
@@ -148,6 +150,8 @@
panelIosHighlightUnderlay: '#313035',
panelSecondaryForeground: '#333333',
panelSecondaryForegroundBorder: '#666666',
+ purpleLink: '#AE94DB',
+ purpleButton: '#7E57C2',
redButton: '#FF4444',
redText: '#FF4444',
spoiler: '#33332C',

File Metadata

Mime Type
text/plain
Expires
Mon, Jan 12, 6:02 AM (21 m, 53 s)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5921948
Default Alt Text
D5831.1768197756.diff (9 KB)

Event Timeline