Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F32906700
D5831.1768197756.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
9 KB
Referenced Files
None
Subscribers
None
D5831.1768197756.diff
View Options
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 </Text>
+ <Text style={styles.link} onPress={onTermsOfUsePressed}>
+ Terms of Service
+ </Text>
+ <Text> & </Text>
+ <Text style={styles.link} onPress={onPrivacyPolicyPressed}>
+ Privacy Policy
+ </Text>
+ <Text>
+ . We'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
Details
Attached
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)
Attached To
Mode
D5831: [native] Terms of Use & Privacy modal
Attached
Detach File
Event Timeline
Log In to Comment