@umituz/react-native-settings 5.4.17 → 5.4.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/base/BaseService.d.ts +86 -0
- package/dist/core/index.d.ts +27 -0
- package/dist/core/patterns/Modal/ModalConfig.d.ts +197 -0
- package/dist/core/patterns/Modal/useModalState.d.ts +53 -0
- package/dist/core/patterns/Screen/ScreenConfig.d.ts +273 -0
- package/dist/core/patterns/Screen/useScreenData.d.ts +62 -0
- package/dist/core/utils/logger.d.ts +76 -0
- package/dist/core/utils/validators.d.ts +114 -0
- package/dist/domains/ai-consent/index.d.ts +37 -0
- package/dist/domains/ai-consent/presentation/components/AIConsentModal.d.ts +26 -0
- package/dist/domains/ai-consent/presentation/components/AIConsentSetting.d.ts +28 -0
- package/dist/domains/ai-consent/presentation/hooks/useAIConsent.d.ts +27 -0
- package/dist/domains/ai-consent/presentation/screens/AIConsentScreen.d.ts +37 -0
- package/dist/domains/disclaimer/index.d.ts +0 -2
- package/dist/domains/disclaimer/presentation/components/DisclaimerSetting.d.ts +1 -2
- package/dist/domains/disclaimer/presentation/screens/DisclaimerScreen.d.ts +11 -20
- package/dist/domains/feedback/index.d.ts +2 -1
- package/dist/domains/feedback/presentation/components/SupportSection.d.ts +1 -1
- package/dist/domains/feedback/presentation/screens/FeedbackScreen.d.ts +21 -0
- package/dist/domains/gamification/types/index.d.ts +1 -0
- package/dist/domains/gamification/utils/calculations.d.ts +3 -0
- package/dist/domains/localization/index.d.ts +1 -1
- package/dist/domains/localization/infrastructure/config/i18n.d.ts +1 -1
- package/dist/domains/notifications/infrastructure/services/NotificationService.d.ts +5 -2
- package/dist/domains/notifications/quietHours/infrastructure/hooks/useQuietHoursActions.d.ts +4 -4
- package/dist/domains/rating/application/services/RatingService.d.ts +50 -21
- package/dist/domains/rating/index.d.ts +2 -2
- package/dist/domains/rating/presentation/hooks/useAppRating.d.ts +1 -1
- package/dist/domains/rating/presentation/screens/RatingPromptScreen.d.ts +22 -0
- package/dist/index.d.ts +4 -0
- package/dist/infrastructure/services/SettingsService.d.ts +5 -2
- package/dist/presentation/components/GenericModal.d.ts +35 -0
- package/dist/presentation/components/GenericScreen.d.ts +41 -0
- package/dist/presentation/components/index.d.ts +17 -0
- package/dist/presentation/navigation/hooks/useSettingsNavigation.d.ts +21 -15
- package/dist/presentation/navigation/types.d.ts +8 -0
- package/dist/presentation/navigation/utils/navigationHelpers.d.ts +1 -1
- package/package.json +1 -1
- package/src/domains/ai-consent/index.ts +53 -0
- package/src/domains/ai-consent/presentation/components/AIConsentModal.tsx +64 -0
- package/src/domains/ai-consent/presentation/components/AIConsentSetting.tsx +106 -0
- package/src/domains/ai-consent/presentation/hooks/useAIConsent.ts +125 -0
- package/src/domains/ai-consent/presentation/screens/AIConsentScreen.tsx +414 -0
- package/src/index.ts +3 -0
- package/src/presentation/navigation/hooks/useSettingsScreens.ts +9 -1
- package/src/presentation/navigation/types.ts +2 -0
package/dist/index.d.ts
CHANGED
|
@@ -7,12 +7,15 @@
|
|
|
7
7
|
* Usage:
|
|
8
8
|
* import { useSettings, SettingsScreen, AppearanceScreen, SettingsItemCard, DisclaimerSetting } from '@umituz/react-native-settings';
|
|
9
9
|
*/
|
|
10
|
+
export * from './core';
|
|
11
|
+
export * from './presentation/components';
|
|
10
12
|
export type { ISettingsRepository, UserSettings, SettingsError, SettingsResult, } from './application/ports/ISettingsRepository';
|
|
11
13
|
export { getSettingsService } from './infrastructure/services/SettingsService';
|
|
12
14
|
export { SettingsRepository } from './infrastructure/repositories/SettingsRepository';
|
|
13
15
|
export { useSettings } from './presentation/hooks/useSettings';
|
|
14
16
|
export { useSettingsQuery } from './presentation/hooks/queries/useSettingsQuery';
|
|
15
17
|
export { useUpdateSettingsMutation, useResetSettingsMutation } from './presentation/hooks/mutations/useSettingsMutations';
|
|
18
|
+
export { useSettingsScreenConfig, type SettingsFeatures, type UseSettingsScreenConfigParams, type SettingsScreenConfigResult } from './presentation/hooks/useSettingsScreenConfig';
|
|
16
19
|
export { SettingsScreen } from './presentation/screens/SettingsScreen';
|
|
17
20
|
export type { SettingsScreenProps } from './presentation/screens/SettingsScreen';
|
|
18
21
|
export { AppearanceScreen } from './presentation/screens/AppearanceScreen';
|
|
@@ -37,5 +40,6 @@ export * from "./domains/cloud-sync";
|
|
|
37
40
|
export * from "./domains/dev";
|
|
38
41
|
export * from "./domains/gamification";
|
|
39
42
|
export * from "./domains/localization";
|
|
43
|
+
export * from "./domains/ai-consent";
|
|
40
44
|
export * from "./domains/notifications";
|
|
41
45
|
export { createAppearanceConfig, createLanguageConfig, createNotificationsConfig, createAboutConfig, createLegalConfig, createFeedbackConfig, createRatingConfig, createFAQConfig, createSubscriptionConfig, createGamificationConfig, type TranslationFunction, type FeedbackFormData, } from './presentation/utils';
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Settings Service
|
|
3
3
|
*
|
|
4
|
-
* Orchestrates settings operations using SettingsRepository
|
|
4
|
+
* Orchestrates settings operations using SettingsRepository.
|
|
5
|
+
* Refactored to extend BaseService for consistent error handling.
|
|
5
6
|
*/
|
|
7
|
+
import { BaseService } from '../../core/base/BaseService';
|
|
6
8
|
import type { UserSettings, SettingsResult } from '../../application/ports/ISettingsRepository';
|
|
7
|
-
export declare class SettingsService {
|
|
9
|
+
export declare class SettingsService extends BaseService {
|
|
10
|
+
protected serviceName: string;
|
|
8
11
|
private repository;
|
|
9
12
|
constructor();
|
|
10
13
|
getSettings(userId: string): Promise<SettingsResult<UserSettings>>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GenericModal Component
|
|
3
|
+
*
|
|
4
|
+
* Universal modal component that works with ModalConfig.
|
|
5
|
+
* Replaces all custom modal implementations.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* const modal = useModalState();
|
|
10
|
+
*
|
|
11
|
+
* return (
|
|
12
|
+
* <>
|
|
13
|
+
* <Button onPress={() => modal.show(ModalPresets.confirm(...))} />
|
|
14
|
+
* <GenericModal state={modal} />
|
|
15
|
+
* </>
|
|
16
|
+
* );
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
import React from 'react';
|
|
20
|
+
import type { ModalState } from '../../core/patterns/Modal/ModalConfig';
|
|
21
|
+
export interface GenericModalProps {
|
|
22
|
+
/**
|
|
23
|
+
* Modal state from useModalState hook
|
|
24
|
+
*/
|
|
25
|
+
state: ModalState;
|
|
26
|
+
/**
|
|
27
|
+
* Custom container style
|
|
28
|
+
*/
|
|
29
|
+
containerStyle?: object;
|
|
30
|
+
/**
|
|
31
|
+
* Test ID for E2E testing
|
|
32
|
+
*/
|
|
33
|
+
testID?: string;
|
|
34
|
+
}
|
|
35
|
+
export declare const GenericModal: React.FC<GenericModalProps>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GenericScreen Component
|
|
3
|
+
*
|
|
4
|
+
* Universal screen component that works with ScreenConfig.
|
|
5
|
+
* Handles loading, error, and empty states automatically.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* const screenData = useScreenData({ fetch: async () => await api.getData() });
|
|
10
|
+
*
|
|
11
|
+
* return (
|
|
12
|
+
* <GenericScreen
|
|
13
|
+
* data={screenData}
|
|
14
|
+
* config={ScreenPresets.default}
|
|
15
|
+
* >
|
|
16
|
+
* <MyContent data={screenData.data} />
|
|
17
|
+
* </GenericScreen>
|
|
18
|
+
* );
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
import React from 'react';
|
|
22
|
+
import type { ScreenConfig, ScreenData } from '../../core/patterns/Screen/ScreenConfig';
|
|
23
|
+
export interface GenericScreenProps<T> {
|
|
24
|
+
/**
|
|
25
|
+
* Screen data from useScreenData hook
|
|
26
|
+
*/
|
|
27
|
+
data: ScreenData<T>;
|
|
28
|
+
/**
|
|
29
|
+
* Screen configuration
|
|
30
|
+
*/
|
|
31
|
+
config?: ScreenConfig;
|
|
32
|
+
/**
|
|
33
|
+
* Content component (renders when data is loaded)
|
|
34
|
+
*/
|
|
35
|
+
children: React.ReactNode | ((data: T) => React.ReactNode);
|
|
36
|
+
/**
|
|
37
|
+
* Test ID for E2E testing
|
|
38
|
+
*/
|
|
39
|
+
testID?: string;
|
|
40
|
+
}
|
|
41
|
+
export declare function GenericScreen<T>({ data, config, children, testID, }: GenericScreenProps<T>): React.JSX.Element;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Presentation Components - Public API
|
|
3
|
+
*
|
|
4
|
+
* Shared UI components for the entire application.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { GenericModal, GenericScreen } from '@/presentation/components';
|
|
9
|
+
*
|
|
10
|
+
* const modal = useModalState();
|
|
11
|
+
* return <GenericModal state={modal} />;
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export { GenericModal } from './GenericModal';
|
|
15
|
+
export type { GenericModalProps } from './GenericModal';
|
|
16
|
+
export { GenericScreen } from './GenericScreen';
|
|
17
|
+
export type { GenericScreenProps } from './GenericScreen';
|
|
@@ -1,22 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Settings Navigation Hook
|
|
3
3
|
*
|
|
4
|
-
* Provides
|
|
5
|
-
*
|
|
6
|
-
*/
|
|
7
|
-
import type { StackNavigationProp } from '@react-navigation/stack';
|
|
8
|
-
import type { SettingsStackParamList } from '../types';
|
|
9
|
-
/**
|
|
10
|
-
* Type for Settings navigation prop
|
|
11
|
-
*/
|
|
12
|
-
export type SettingsNavigationProp = StackNavigationProp<SettingsStackParamList>;
|
|
13
|
-
/**
|
|
14
|
-
* Hook to get typed navigation for Settings screens
|
|
4
|
+
* Provides standardized navigation for Settings stack screens.
|
|
5
|
+
* Uses useAppNavigation from design system for consistency.
|
|
15
6
|
*
|
|
16
7
|
* @example
|
|
17
8
|
* ```typescript
|
|
18
|
-
*
|
|
19
|
-
*
|
|
9
|
+
* import { useSettingsNavigation } from '@umituz/react-native-settings/presentation/navigation';
|
|
10
|
+
*
|
|
11
|
+
* function LanguageSelectionScreen() {
|
|
12
|
+
* const navigation = useSettingsNavigation();
|
|
13
|
+
* navigation.navigate('Appearance');
|
|
14
|
+
* navigation.goBack();
|
|
15
|
+
* }
|
|
20
16
|
* ```
|
|
21
17
|
*/
|
|
22
|
-
|
|
18
|
+
import { useAppNavigation } from '@umituz/react-native-design-system/molecules';
|
|
19
|
+
/**
|
|
20
|
+
* Navigation result type inferred from useAppNavigation
|
|
21
|
+
*/
|
|
22
|
+
type SettingsNavigation = ReturnType<typeof useAppNavigation>;
|
|
23
|
+
/**
|
|
24
|
+
* Hook to get navigation for Settings screens
|
|
25
|
+
* Delegates to useAppNavigation for consistent API
|
|
26
|
+
*/
|
|
27
|
+
export declare const useSettingsNavigation: () => SettingsNavigation;
|
|
28
|
+
export {};
|
|
@@ -5,6 +5,10 @@ import type React from 'react';
|
|
|
5
5
|
import type { SettingsConfig, CustomSettingsSection } from "../screens/types";
|
|
6
6
|
import type { DevSettingsProps } from "../../domains/dev";
|
|
7
7
|
import type { FAQCategory } from "../../domains/faqs";
|
|
8
|
+
import type { DisclaimerScreenParams } from "../../domains/disclaimer/presentation/screens/DisclaimerScreen";
|
|
9
|
+
import type { FeedbackScreenParams } from "../../domains/feedback/presentation/screens/FeedbackScreen";
|
|
10
|
+
import type { RatingPromptScreenParams } from "../../domains/rating/presentation/screens/RatingPromptScreen";
|
|
11
|
+
import type { AIConsentScreenParams } from "../../domains/ai-consent/presentation/screens/AIConsentScreen";
|
|
8
12
|
/**
|
|
9
13
|
* App Info passed from main app (APP_INFO constant)
|
|
10
14
|
*/
|
|
@@ -44,6 +48,10 @@ export type SettingsStackParamList = {
|
|
|
44
48
|
Account: undefined;
|
|
45
49
|
VideoTutorial: undefined;
|
|
46
50
|
FeatureRequest: undefined;
|
|
51
|
+
Disclaimer: DisclaimerScreenParams;
|
|
52
|
+
Feedback: FeedbackScreenParams;
|
|
53
|
+
RatingPrompt: RatingPromptScreenParams;
|
|
54
|
+
AIConsent: AIConsentScreenParams;
|
|
47
55
|
PasswordPrompt: {
|
|
48
56
|
onComplete: (password: string | null) => void;
|
|
49
57
|
title?: string;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import type { SettingsStackParamList } from '../types';
|
|
6
6
|
type NavigateFunction = <RouteName extends keyof SettingsStackParamList>(route: RouteName, params?: SettingsStackParamList[RouteName]) => void;
|
|
7
7
|
interface RouteOrPressConfig<T extends keyof SettingsStackParamList = keyof SettingsStackParamList> {
|
|
8
|
-
route?: T;
|
|
8
|
+
route?: T | string;
|
|
9
9
|
onPress?: () => void;
|
|
10
10
|
fallback?: T;
|
|
11
11
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-settings",
|
|
3
|
-
"version": "5.4.
|
|
3
|
+
"version": "5.4.19",
|
|
4
4
|
"description": "Complete settings hub for React Native apps - consolidated package with settings, localization, about, legal, appearance, feedback, FAQs, rating, and gamification - expo-store-review and expo-device now lazy loaded",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @umituz/react-native-settings - AI Consent Domain
|
|
3
|
+
*
|
|
4
|
+
* AI consent management for React Native apps
|
|
5
|
+
* Required by Apple App Store Guidelines 5.1.1(i) & 5.1.2(i)
|
|
6
|
+
*
|
|
7
|
+
* Displays AI technology disclosure and obtains user consent before
|
|
8
|
+
* using AI generation features.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* import {
|
|
12
|
+
* AIConsentScreen,
|
|
13
|
+
* AIConsentModal,
|
|
14
|
+
* AIConsentSetting,
|
|
15
|
+
* useAIConsent
|
|
16
|
+
* } from '@umituz/react-native-settings/ai-consent';
|
|
17
|
+
*
|
|
18
|
+
* // Show modal on app launch
|
|
19
|
+
* const { isConsentModalVisible, handleAcceptConsent, handleDeclineConsent } = useAIConsent();
|
|
20
|
+
*
|
|
21
|
+
* <AIConsentModal
|
|
22
|
+
* visible={isConsentModalVisible}
|
|
23
|
+
* onAccept={handleAcceptConsent}
|
|
24
|
+
* onDecline={handleDeclineConsent}
|
|
25
|
+
* />
|
|
26
|
+
*
|
|
27
|
+
* // Add to settings screen
|
|
28
|
+
* <AIConsentSetting />
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
// =============================================================================
|
|
32
|
+
// PRESENTATION LAYER - Components
|
|
33
|
+
// =============================================================================
|
|
34
|
+
|
|
35
|
+
export { AIConsentModal } from './presentation/components/AIConsentModal';
|
|
36
|
+
export type { AIConsentModalProps } from './presentation/components/AIConsentModal';
|
|
37
|
+
|
|
38
|
+
export { AIConsentSetting } from './presentation/components/AIConsentSetting';
|
|
39
|
+
export type { AIConsentSettingProps } from './presentation/components/AIConsentSetting';
|
|
40
|
+
|
|
41
|
+
// =============================================================================
|
|
42
|
+
// PRESENTATION LAYER - Screens
|
|
43
|
+
// =============================================================================
|
|
44
|
+
|
|
45
|
+
export { AIConsentScreen } from './presentation/screens/AIConsentScreen';
|
|
46
|
+
export type { AIConsentScreenProps, AIConsentScreenParams, AIProvider } from './presentation/screens/AIConsentScreen';
|
|
47
|
+
|
|
48
|
+
// =============================================================================
|
|
49
|
+
// PRESENTATION LAYER - Hooks
|
|
50
|
+
// =============================================================================
|
|
51
|
+
|
|
52
|
+
export { useAIConsent } from './presentation/hooks/useAIConsent';
|
|
53
|
+
export type { UseAIConsentReturn, AIConsentState } from './presentation/hooks/useAIConsent';
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Consent Modal
|
|
3
|
+
*
|
|
4
|
+
* Modal wrapper for AI consent screen.
|
|
5
|
+
* Required by Apple App Store Guidelines 5.1.1(i) & 5.1.2(i)
|
|
6
|
+
*
|
|
7
|
+
* Displays on first app launch before any AI features are used.
|
|
8
|
+
* Can also be shown manually from settings.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* <AIConsentModal
|
|
12
|
+
* visible={isConsentModalVisible}
|
|
13
|
+
* onAccept={handleAccept}
|
|
14
|
+
* onDecline={handleDecline}
|
|
15
|
+
* />
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import React, { memo } from 'react';
|
|
19
|
+
import { Modal, View, StyleSheet } from 'react-native';
|
|
20
|
+
import { AIConsentScreen, type AIProvider } from '../screens/AIConsentScreen';
|
|
21
|
+
|
|
22
|
+
export interface AIConsentModalProps {
|
|
23
|
+
visible: boolean;
|
|
24
|
+
onAccept: () => void;
|
|
25
|
+
onDecline: () => void;
|
|
26
|
+
providers?: AIProvider[];
|
|
27
|
+
customMessage?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const AIConsentModal: React.FC<AIConsentModalProps> = memo(({
|
|
31
|
+
visible,
|
|
32
|
+
onAccept,
|
|
33
|
+
onDecline,
|
|
34
|
+
providers,
|
|
35
|
+
customMessage,
|
|
36
|
+
}) => {
|
|
37
|
+
return (
|
|
38
|
+
<Modal
|
|
39
|
+
visible={visible}
|
|
40
|
+
animationType="slide"
|
|
41
|
+
transparent={false}
|
|
42
|
+
onRequestClose={onDecline}
|
|
43
|
+
>
|
|
44
|
+
<View style={styles.container}>
|
|
45
|
+
<AIConsentScreen
|
|
46
|
+
providers={providers}
|
|
47
|
+
customMessage={customMessage}
|
|
48
|
+
onAccept={onAccept}
|
|
49
|
+
onDecline={onDecline}
|
|
50
|
+
standalone={true}
|
|
51
|
+
/>
|
|
52
|
+
</View>
|
|
53
|
+
</Modal>
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
AIConsentModal.displayName = 'AIConsentModal';
|
|
58
|
+
|
|
59
|
+
const styles = StyleSheet.create({
|
|
60
|
+
container: {
|
|
61
|
+
flex: 1,
|
|
62
|
+
backgroundColor: '#FFFFFF',
|
|
63
|
+
},
|
|
64
|
+
});
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AIConsentSetting Component
|
|
3
|
+
*
|
|
4
|
+
* Settings list item for AI consent management.
|
|
5
|
+
* Shows current consent status and navigates to full consent screen.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Displays consent status (accepted/declined/pending)
|
|
9
|
+
* - Tappable card that opens AI consent screen
|
|
10
|
+
* - Icon with background color
|
|
11
|
+
* - Internationalized
|
|
12
|
+
* - Universal across iOS, Android, Web
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* import { AIConsentSetting } from '@umituz/react-native-settings/ai-consent';
|
|
16
|
+
*
|
|
17
|
+
* <AIConsentSetting />
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import React, { useCallback, memo } from 'react';
|
|
21
|
+
import { View, StyleSheet } from 'react-native';
|
|
22
|
+
import { useAppDesignTokens, withAlpha } from '@umituz/react-native-design-system/theme';
|
|
23
|
+
import { useAppNavigation } from '@umituz/react-native-design-system/molecules';
|
|
24
|
+
import { AtomicText } from '@umituz/react-native-design-system/atoms';
|
|
25
|
+
import { useAIConsent } from '../hooks/useAIConsent';
|
|
26
|
+
|
|
27
|
+
export interface AIConsentSettingProps {
|
|
28
|
+
/** Custom title */
|
|
29
|
+
title?: string;
|
|
30
|
+
/** Custom description when consented */
|
|
31
|
+
consentedDescription?: string;
|
|
32
|
+
/** Custom description when not consented */
|
|
33
|
+
notConsentedDescription?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const AIConsentSetting: React.FC<AIConsentSettingProps> = memo(({
|
|
37
|
+
title = 'AI Technology Consent',
|
|
38
|
+
consentedDescription = 'You have consented to use AI services',
|
|
39
|
+
notConsentedDescription = 'Review AI service disclosure',
|
|
40
|
+
}) => {
|
|
41
|
+
const tokens = useAppDesignTokens();
|
|
42
|
+
const navigation = useAppNavigation();
|
|
43
|
+
const { hasConsented, isLoading } = useAIConsent();
|
|
44
|
+
|
|
45
|
+
const handlePress = useCallback(() => {
|
|
46
|
+
navigation.push('AIConsent' as never);
|
|
47
|
+
}, [navigation]);
|
|
48
|
+
|
|
49
|
+
if (isLoading) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const iconColor = hasConsented ? tokens.colors.success : tokens.colors.warning;
|
|
54
|
+
const backgroundColor = withAlpha(iconColor, 0.1);
|
|
55
|
+
const description = hasConsented ? consentedDescription : notConsentedDescription;
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<View style={[styles.container, { backgroundColor }]}>
|
|
59
|
+
<View style={[styles.iconContainer, { backgroundColor: iconColor }]}>
|
|
60
|
+
<AtomicText style={styles.icon}>🤖</AtomicText>
|
|
61
|
+
</View>
|
|
62
|
+
|
|
63
|
+
<View style={styles.content}>
|
|
64
|
+
<AtomicText style={styles.title}>{title}</AtomicText>
|
|
65
|
+
<AtomicText style={styles.description}>{description}</AtomicText>
|
|
66
|
+
</View>
|
|
67
|
+
</View>
|
|
68
|
+
);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
AIConsentSetting.displayName = 'AIConsentSetting';
|
|
72
|
+
|
|
73
|
+
const styles = StyleSheet.create({
|
|
74
|
+
container: {
|
|
75
|
+
flexDirection: 'row',
|
|
76
|
+
alignItems: 'center',
|
|
77
|
+
padding: 16,
|
|
78
|
+
borderRadius: 12,
|
|
79
|
+
marginVertical: 8,
|
|
80
|
+
},
|
|
81
|
+
iconContainer: {
|
|
82
|
+
width: 48,
|
|
83
|
+
height: 48,
|
|
84
|
+
borderRadius: 24,
|
|
85
|
+
alignItems: 'center',
|
|
86
|
+
justifyContent: 'center',
|
|
87
|
+
marginRight: 16,
|
|
88
|
+
},
|
|
89
|
+
icon: {
|
|
90
|
+
fontSize: 24,
|
|
91
|
+
},
|
|
92
|
+
content: {
|
|
93
|
+
flex: 1,
|
|
94
|
+
},
|
|
95
|
+
title: {
|
|
96
|
+
fontSize: 16,
|
|
97
|
+
fontWeight: '600',
|
|
98
|
+
color: '#111827',
|
|
99
|
+
marginBottom: 4,
|
|
100
|
+
},
|
|
101
|
+
description: {
|
|
102
|
+
fontSize: 14,
|
|
103
|
+
color: '#6B7280',
|
|
104
|
+
lineHeight: 20,
|
|
105
|
+
},
|
|
106
|
+
});
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useAIConsent Hook
|
|
3
|
+
* Manages AI consent state and display logic
|
|
4
|
+
* Required by Apple App Store Guidelines 5.1.1(i) & 5.1.2(i)
|
|
5
|
+
*
|
|
6
|
+
* Features:
|
|
7
|
+
* - Checks if user has consented to AI services
|
|
8
|
+
* - Shows modal on first use
|
|
9
|
+
* - Persists consent state
|
|
10
|
+
* - Memoized for performance
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
14
|
+
import { useStorage } from '@umituz/react-native-design-system/storage';
|
|
15
|
+
|
|
16
|
+
export interface AIConsentState {
|
|
17
|
+
hasConsented: boolean;
|
|
18
|
+
consentTimestamp?: number;
|
|
19
|
+
consentVersion?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const CONSENT_STORAGE_KEY = '@app:ai_consent_accepted';
|
|
23
|
+
const CURRENT_CONSENT_VERSION = '1.0';
|
|
24
|
+
|
|
25
|
+
export interface UseAIConsentReturn {
|
|
26
|
+
isLoading: boolean;
|
|
27
|
+
isConsentModalVisible: boolean;
|
|
28
|
+
hasConsented: boolean;
|
|
29
|
+
consentState: AIConsentState | null;
|
|
30
|
+
handleAcceptConsent: () => Promise<void>;
|
|
31
|
+
handleDeclineConsent: () => void;
|
|
32
|
+
showConsentModal: () => void;
|
|
33
|
+
checkConsent: () => Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const useAIConsent = (): UseAIConsentReturn => {
|
|
37
|
+
const { getString, setString } = useStorage();
|
|
38
|
+
const [isConsentModalVisible, setIsConsentModalVisible] = useState(false);
|
|
39
|
+
const [hasConsented, setHasConsented] = useState(false);
|
|
40
|
+
const [consentState, setConsentState] = useState<AIConsentState | null>(null);
|
|
41
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Check if user has previously consented
|
|
45
|
+
*/
|
|
46
|
+
const checkConsent = useCallback(async () => {
|
|
47
|
+
setIsLoading(true);
|
|
48
|
+
try {
|
|
49
|
+
const consentData = await getString(CONSENT_STORAGE_KEY, '');
|
|
50
|
+
|
|
51
|
+
if (consentData) {
|
|
52
|
+
const parsed: AIConsentState = JSON.parse(consentData);
|
|
53
|
+
setConsentState(parsed);
|
|
54
|
+
setHasConsented(parsed.hasConsented);
|
|
55
|
+
|
|
56
|
+
// Show modal if user hasn't consented
|
|
57
|
+
if (!parsed.hasConsented) {
|
|
58
|
+
setIsConsentModalVisible(true);
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
// No consent found - show modal
|
|
62
|
+
setIsConsentModalVisible(true);
|
|
63
|
+
}
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error('[useAIConsent] Failed to check consent:', error);
|
|
66
|
+
// On error, show modal to be safe
|
|
67
|
+
setIsConsentModalVisible(true);
|
|
68
|
+
} finally {
|
|
69
|
+
setIsLoading(false);
|
|
70
|
+
}
|
|
71
|
+
}, [getString]);
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Handle user accepting AI consent
|
|
75
|
+
*/
|
|
76
|
+
const handleAcceptConsent = useCallback(async () => {
|
|
77
|
+
try {
|
|
78
|
+
const newState: AIConsentState = {
|
|
79
|
+
hasConsented: true,
|
|
80
|
+
consentTimestamp: Date.now(),
|
|
81
|
+
consentVersion: CURRENT_CONSENT_VERSION,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
await setString(CONSENT_STORAGE_KEY, JSON.stringify(newState));
|
|
85
|
+
setConsentState(newState);
|
|
86
|
+
setHasConsented(true);
|
|
87
|
+
setIsConsentModalVisible(false);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.error('[useAIConsent] Failed to save consent:', error);
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
}, [setString]);
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Handle user declining AI consent
|
|
96
|
+
*/
|
|
97
|
+
const handleDeclineConsent = useCallback(() => {
|
|
98
|
+
setIsConsentModalVisible(false);
|
|
99
|
+
// User declined - they can still use the app but AI features will be blocked
|
|
100
|
+
// This is handled by the hasConsented flag
|
|
101
|
+
}, []);
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Manually show consent modal (e.g., from settings)
|
|
105
|
+
*/
|
|
106
|
+
const showConsentModal = useCallback(() => {
|
|
107
|
+
setIsConsentModalVisible(true);
|
|
108
|
+
}, []);
|
|
109
|
+
|
|
110
|
+
// Check consent on mount
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
void checkConsent();
|
|
113
|
+
}, [checkConsent]);
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
isLoading,
|
|
117
|
+
isConsentModalVisible,
|
|
118
|
+
hasConsented,
|
|
119
|
+
consentState,
|
|
120
|
+
handleAcceptConsent,
|
|
121
|
+
handleDeclineConsent,
|
|
122
|
+
showConsentModal,
|
|
123
|
+
checkConsent,
|
|
124
|
+
};
|
|
125
|
+
};
|