import {Observable} from "rxjs";
//$FlowFixMe
import {Store} from "redux";
/**
* Redux reducers implemented in app
* @typedef {"accountSettings" | "documentsCache" | "documents" | "activities" | "activitiesCache" | "albums" | "appFeedback" | "auth" | "blockData" | "blockUser" | "blockLoadableDataCache" | "blockLoadableDataError" | "blockLoadableDataLoading" | "blog" | "blogCache" | "blogSingleComments" | "blogSingleCommentsCache" | "config" | "courses" | "courseCategories" | "coursesCache" | "coursesByCategory" | "emailInvites" | "featureStatuses" | "firebase" | "fonts" | "forgot" | "forums" | "forumsCache" | "friendRequests" | "gifs" | "groupDocuments" | "groupActivities" | "groupMembers" | "groups" | "groupsCache" | "groupInvites" | "groupMessages" | "groupRequests" | "groupCourses" | "groupSettings" | "localAuthentication" | "media" | "files" | "preview" | "members" | "messages" | "messagesCache" | "thread" | "menuSettings" | "merge" | "move" | "notifications" | "notificationsCache" | "privacySettings" | "profileDocuments" | "profileActivities" | "profileBlog" | "profileCourses" | "groupAlbums" | "groupPhotos" | "globalPhotos" | "profilePhotos" | "hostedPhotos" | "photos" | "mediaCache" | "profileAlbums" | "albumsCache" | "profileFriends" | "profileGroups" | "profileTopics" | "profileTabs" | "tagTopics" | "profileReplies" | "repliesCache" | "reportContentCategories" | "reportContentRequest" | "settings" | "signup" | "signupFields" | "singleAlbumPhotos" | "singleCourse" | "singleGroup" | "singleForum" | "singleLearnTopic" | "singleLesson" | "singleQuiz" | "singleQuizQuestions" | "singleTopic" | "sites" | "socialGroups" | "split" | "survey" | "subgroups" | "cardModal" | "groupTabs" | "topicCache" | "topicDropdown" | "topics" | "trackPlayer" | "urls" | "user" | "userProfile" | "usersCache" | "xProfile" | "inAppPurchases" | "appInitialisation" | "profileDetails" | "notificationSubscriptions" | "network" } AppReducers
*/
/**
* Redux reducers implemented in app
* @typedef {"loadEmailPreferences" | "updateEmailPreferences" | "getEmailInvites" | "revokeEmailInvite" | "checkBiometricSupport" | "checkBiometricEnrollmentStatus" | "sendAppFeedback" | "fetchPhotoActivity" | "createNewPhotos" | "deletePhotos" | "updatePhoto" | "albumsTree" | "loadHostedPhotos" | "loadAlbumPhotos" | "editAlbum" | "loadProfileBlog" | "fetchPost" | "loadBlog" | "loadBlogSingleComments" | "loadBlogCategories" | "loadBlogCreationTimes" | "deleteBlogComment" | "blogComment" | "loadGroupTypes" | "loadProfileTypes" | "viewAsRestore" | "viewAsSwitch" | "deleteAccount" | "loadAccountSettings" | "loadPrivacySettings" | "updatePrivacySettings" | "loadLoginInfo" | "updateLoginInfo" | "loadGroupInviteSettings" | "updateGroupInviteSettings" | "fetchActivity" | "updateActivity" | "updateViewAsSession" | "restNoRouteHandler" | "removeFriend" | "loadFriendRequests" | "friendRequestCreate" | "friendRequestModify" | "loadGroupInvites" | "groupInviteModify" | "groupInviteCreate" | "loadGroupRequests" | "groupRequestCreate" | "loadProfileTopics" | "loadTagTopics" | "groupRequestModify" | "loadSignupFields" | "signup" | "socialLogin" | "forgot" | "getUser" | "getAnyUser" | "navigateToProfile" | "loadProfileReplies" | "navigateToGroup" | "loadUserProfileData" | "loadTopicForUser" | "loadSubGroups" | "loadGroups" | "joinGroup" | "leaveGroup" | "groupsDetails" | "loadSocialGroups" | "joinSocialGroup" | "loadSingleSocialGroup" | "loadGroupSettings" | "socialGroupTabRequest" | "notificationAction" | "loadNotifications" | "markNotification" | "deleteNotification" | "loadForums" | "subscribeForum" | "unsubscribeForum" | "navigateToSubforum" | "loadTopics" | "fetchTopic" | "subscribeTopic" | "unsubscribeTopic" | "newTopicEpic" | "closeKeyBoard" | "blockUser" | "unblockUser" | "loadSingleForum" | "loadForumFromSubForum" | "loadRepliesForTopic" | "unfavoriteTopic" | "favoriteTopic" | "openTopic" | "closeTopic" | "stickTopic" | "unstickTopic" | "superstickTopic" | "spamTopic" | "unspamTopic" | "trashTopic" | "loadRelatedTopicsForTopic" | "spamReply" | "unspamReply" | "trashReply" | "newReply" | "refreshRepliesForTopic" | "loadDropdownTopics" | "doMergeEpic" | "doSplitEpic" | "doMoveEpic" | "uploadMedia" | "uploadDocument" | "getPreviewData" | "loadMenuSettings" | "loadSites" | "loadCoursesOnRequest" | "loadCoursesByCategoryOnRequest" | "loadCoursesWithCategoriesOnRequest" | "loadSingleCourse" | "loadSingleCourseMembers" | "loadSingleTopic" | "completeTopic" | "coursesDetails" | "enrollCourse" | "loadSingleLessonWithTopics" | "completeLesson" | "loadSingleQuiz" | "startQuizEpic" | "completeQuiz" | "updateSettings" | "loadReportCategories" | "reportContentRequest" | "clearCookiesOnLogout" | "loadFriendsToList" | "loadGroupDocumentsToList" | "loadProfileDocumentsToList" | "loadProfileActivitiesToList" | "loadProfileGroups" | "loadGroupMembersToList" | "loadGroupActivitiesToList" | "loadMembersToList" | "membersDetails" | "loadMembersAdvancedSearchForm" | "loadActivitiesToList" | "loadMessagesToList" | "loadThread" | "deleteThread" | "actionThread" | "newMessage" | "favoriteActivity" | "pinActivity" | "replyToActivity" | "deleteActivity" | "newActivity" | "documentsDetails" | "foldersTree" | "fetchFolder" | "loadDocumentsToList" | "deleteDocument" | "newDocument" | "updateDocument" | "activitiesDetails" | "followMember" | "resetStateOnLogout" | "blockDataRequest" | "loadBlockContent" | "memberProfileFields" | "memberProfileFieldsEdit" | "uploadProfileImage" | "deleteUserPhoto" | "uploadGroupImage" | "uploadGroupCover" | "groupUpdate" | "deleteGroup" | "deleteGroupCover" | "deleteGroupPhoto" | "promoteMember" | "bulkResources" | "surveyEntriesRequest" | "surveyFormDataRequest" | "surveyResultsRequest" | "loadInAppProducts" | "makePurchase" | "registerPurchaseListeners" | "loadProductsForScreen" | "applyLoadedData" | "loadDataForAppInitialisation" | "loadUsersToCache" | "askFileUploadPermissions" | "navigateToTopic" | "navigateToAddTopic" | "loadNotificationSubscriptions" | "updateNotificationSubscriptions" | "initializeFirebase" | "manageNotifications" | "manageFirePushToken" | "updateFirebaseTopics" | "setNativeBadgeValue" | "notificationClickHandle" | "loadProfileCourses" | "loadProfilePhotos" | "selectSinglePhoto" | "deleteProfilePhoto" | "uploadProfilePhoto" | "fetchProfileAlbums" | "createProfileAlbum" | "setFilterPhoto" | "uploadProfileAlbumPhotoRequest" | "deleteProfileAlbumPhoto" | "deleteProfileAlbum" | "loadPhotoActivity" | "replyToPhotoComment" | "loadPhotoActivityItem" | "photoFavoriteActivity" | "addPhotoComment" | "deletePhotoActivity" | "loadGroupPhotos" | "deleteGroupPhoto" | "uploadGroupPhoto" | "setGroupFilterPhoto" | "fetchGroupAlbums" | "uploadProfileAlbumPhotoRequest" | "deleteGroupAlbumPhoto" | "createGroupAlbum" | "deleteGroupAlbum" | "loadGroupCourses" | "loadGlobalPhotosToList" | "loadGlobalPhotosDetails" | "validateToken" | "loadProfileTabs" | "loadGifs" | "completePurchase" | "addRepeater" | "deleteRepeater" | "reorderRepeaters"} AppEpic
*/
/**
* @typedef {Function} ValidationFunction
* @param { Object } responseObject Object returned in response
* @return {boolean}
*/
/**
* @typedef {Object} CancelablePromise
* @param {Function} cancel
* @param {Promise} promise
* @param {Promise} then then function of 'promise'
* @param {Promise} catch catch function of 'promise'
*/
/**
* @typedef {Function} CustomAPIRequestFunction
* @param { string } urlPath URL
* @param { "post" | "get" | "delete" | "patch" } method
* @param { Object } paramsOrPayload Params to send with request body
* @param { ValidationFunction } validation Runs a validation over response object. You can define your own function or use `objectValidation` (checks if response is an object) or `arrayOfObjectsValidation` (checks if response is an array of objects). If validation fails, app will go in offline mode.
* @param { Object } headers Define request headers
* @param { boolean } isfullUrl Set `true` if you want to use the full URL, instead of appending the path to your connected site URL.
* @returns {CancelablePromise}
*/
/**
* @typedef {Function} TranslationFunction
* @param {string}
* @return {string}
* @example
* t("common:ok") // gives "Ok"
*/
/**
* @typedef {Object} RequestsAPI
* @property {CustomAPIRequestFunction} customRequest Used to make custom API requests
/**
* @typedef {Function} GetAPI
* @param {Object} - App configuration object that you can get from redux store.config.
* @return {RequestsAPI} - Object containing many methods for fetching specific data as well as general method `customRequest` for fetching from any REST API url.
*/
/**
* @typedef {Object} IOMiddlewareOptions
* @property {NavigationService} navigationService Object similar to the navigation property of "@react-navigation/native". The most used methods are `navigate` and`dispatc`. Both are used for triggering navigation.
* @property {BuildConfigType} buildConfig Build configuration object which contains all things required for specific app builds, including URL to the app icon, app launch screen, and many more.
* @property {Object} inApp "react-native-iap" default export object
* @property {GetAPI} getApi Function that gets an instance of the class containing all the methods used for fetching data
* @property {TranslationFunction} t
*/
/**
* Redux action
* @typedef {Object} ReduxAction
* @property {String} type Indicator for the handling of the action
* @see {@link https://redux.js.org/basics/actions}
*/
/**
* Redux reducer
* @typedef {Function} Reducer
* @param {ReduxStore} previousState The current state
* @param {ReduxAction} action An action to execute to update the store
* @return {ReduxStore} The next state
* @see {@link https://redux.js.org/basics/reducers}
*/
/**
* @typedef ReduxStore
* @type {Object}
* @see {@link https://redux.js.org/basics/store}
*/
/**
* @typedef {Function} ReduxMiddleware
* @see {@link https://redux.js.org/understanding/history-and-design/middleware}
*/
/**
* @typedef {Function} ReducerWrapper
* @param {Reducer} - Original reducer
* @return {Reducer} - Changed reducer
*/
/**
* @typedef {Object} StoreApi
* @param {Function} getState Returns redux store object
* @param {Function} dispatch Dispatches the action
*/
/**
* @typedef {Object} Epic
* @param {Observable<ReduxAction>} observable
* @param {StoreApi} storeApi
* @param {IOMiddlewareOptions} dependencies
* @return {Observable<ReduxAction>}
* @see {@link https://redux-observable.js.org/docs/basics/Epics.html}
*/
/**
* @typedef {Function} EpicWrapper
* @param {Epic} - Original epic
* @return {Epic} - Changed epic
*/
/**
* @class
* Redux Hooks.
* Instance name: reduxApi
You can use these hooks to customize the redux related configurations of your app such as adding a new epic, new middleware to store initialization, and more.
* @example
* externalCodeSetup.reduxApi.METHOD_NAME
*/
export class ReduxApi {
newReducers = {};
reducerWrappers = {};
newEpics = {};
epicWrappers = {};
storeCreateListeners = [];
newMiddlewares = [];
persitorConfigChangers = [];
customPersistorConfig = undefined;
isDataPersistingEnabled = true;
/**
* Adds a new reducer to the root reducer.
* Note: If "name" used to add a reducer already exists, the new reducer will replace the old reducer.
* @method
* @param {String} name Name of reducer
* @param {Reducer} reducer Reducer function
* @example <caption> Create a new reducer </caption>
*
* const initialState = {
* loading: false,
* loaded: false,
* errorMessage: null,
* foodReceived: ""
* }
* const delivery = (state = initialState, action) => {
* switch (action.type) {
* case "FOOD_DELIVERY_REQUEST": {
* return {
* ...state,
* loading: true,
* loaded: false,
* errorMessage: null
* };
* }
* case "FOOD_DELIVERY_SUCCESS": {
* return {
* ...state,
* foodReceived: action.foodToDeliver,
* loading: false,
* loaded: true,
* errorMessage: null
* };
* }
* case "FOOD_DELIVERY_FAIL": {
* return {
* ...state,
* loading: false,
* loaded: false,
* errorMessage: action.errorMessage
* };
* }
* default:
* return state;
* }
* };
*
* externalCodeSetup.reduxApi.addReducer("food", delivery)
*
*/
addReducer = (name, reducer) => {
this.newReducers[name] = reducer;
};
/**
* Wraps original reducer with another reducer.
* It can be useful if you want to change original reducer behavior without replacing it.
* @method
* @param {AppReducers} name Name of reducer to wrap
* @param {ReducerWrapper} reducerWrapper
* @example <caption> Add a button which removes an item from the courses list </caption>
*
* //In custom_code/components/CourseItem.js
*
* import React from 'react';
* import { View, Text, TouchableOpacity, Button } from "react-native";
* import { WidgetItemCourseUserConnected } from "@src/components/Widgets/WidgetItemCourseUser";
* import { useDispatch } from "react-redux"
*
* const NewWidgetItemCourseComponent = (props) => {
*
* const { viewModel, global, colors } = props;
*
* const dispatch = useDispatch();
*
* return <View>
* <View style={{ margin: 20, flexDirection: "row" }}>
*
* <TouchableOpacity onPress={viewModel.onClick}>
* <Text>
* {viewModel.title}
* </Text>
*
* <WidgetItemCourseUserConnected
* lightText={true}
* global={global}
* userId={viewModel.authorId}
* colors={colors}
* />
*
* </TouchableOpacity>
* </View>
*
* <View>
* //Use dispatch() from react-redux to dispatch an action
* <Button title="Remove from list"
* onPress={() => dispatch({ type: "COURSE_REMOVE_ITEM", courseToRemove: viewModel.id })} />
* </View>
*
* </View>
*
* }
*
* export default NewWidgetItemCourseComponent;
*
* //In custom_code/index.js...
*
* ...
*
* import CourseItem from "./components/CourseItem"
* export const applyCustomCode = externalCodeSetup => {
*
* externalCodeSetup.coursesHooksApi.setWidgetItemCourseComponent(CourseItem);
*
* const reducerName = "courses"; // "courses" reducer can access data displayed in courses list
*
* //Initialize the custom reducer
* const customReducer = reducer => (state = reducer(undefined, {}), action) => {
*
* switch (action.type) {
*
* case "COURSE_REMOVE_ITEM":
*
* //Use variables below to get index of id which will be removed from the list
* const removeWithIndex = state.all.ids.indexOf(action.courseToRemove);
* const newIds = state.all.ids.splice(removeWithIndex, 1)
*
* //Assign new state with "newIds"
* const newState = {
* ...state,
* all: {
* ...state.all,
* ids: newIds
* }
* }
*
* return reducer(newState, action);
*
* default:
* return reducer(state, action);
* }
*
* }
*
* externalCodeSetup.reduxApi.wrapReducer(
* reducerName,
* customReducer
* );
* }
*
*/
wrapReducer = (name, reducerWrapper) => {
this.reducerWrappers[name] = reducerWrapper;
};
/**
* It adds a new epic to the root epic.
* Note: The new epic will replace the old epic if “name” used to add an epic already exists.
* @method
* @param {String} name Epic name
* @param {Epic} epic
* @example <caption> Call an epic </caption>
*
* ...
*
* import { asObservable } from "@src/epics/rxUtils";
* const { getApi } = require("@src/services");
* import { errorToAction } from "@src/utils";
* export const applyCustomCode = externalCodeSetup => {
*
* const courseRemoveFail = (error) => ({
* type: "COURSE_REMOVE_ERROR",
* error,
* })
* const courseRemoveItem = (
* action$,
* store
* ) =>
* action$
* .filter(
* a =>
* a.type === "COURSE_REMOVE_ITEM" //Observer for type "COURSE_REMOVE_ITEM". If called, proceed with mergeMap function
* )
* .mergeMap(a => {
* let state = store.getState();
* let { config } = state;
*
* //Create a "loading" observable
* const loadingObservable = Observable.of({
* type: "COURSE_REMOVE_LOADING"
* });
*
* const api = getApi(config);
*
* const urlPath = "https://api-to-call.com",
* method = "GET",
* paramsOrPayload = {},
* validation = {},
* isfullUrl = true,
* headers = {
* appid: config.app_id
* }
*
* //Use BuddyBoss helper function "api.customRequest"
* const apiToCall = api
* .customRequest(
* urlPath,
* method,
* paramsOrPayload,
* validation,
* headers,
* isfullUrl
* );
*
* const requestObservable = asObservable(apiToCall)
* .map(() => ({
* type: "COURSE_REMOVE_SUCCESS"
* }))
* .catch(ex =>
* Observable.of(
* errorToAction(ex, e =>
* courseRemoveFail(e)
* )));
* return Observable.concat(loadingObservable, requestObservable);
*
* });
*
* externalCodeSetup.reduxApi.addEpic("courseRemoveItem", courseRemoveItem);
* }
*/
addEpic = (name, epic) => {
this.newEpics[name] = epic;
};
/**
* You can use it to wrap an epic with another epic.
* It can be used to filter action observables of existing epics.
* @method
* @param {AppEpic} name
* @param {EpicWrapper} epicWrapper
* @example <caption> Observes for type "CUSTOM_LOAD_COURSE_REQUEST" in "loadCoursesOnRequest" epic </caption>
* const filterActionsWrapper = (originalEpic: Epic): Epic =>
* (
* action,
* storeApi,
* dependencies
* ) => {
* const filteredInputActions =
* action
* .filter(
* a =>
* a.type === "CUSTOM_LOAD_COURSE_REQUEST"
* )
* return originalEpic(filteredInputActions, storeApi, dependencies);
*
* };
* externalCodeSetup.reduxApi.wrapEpic("loadCoursesOnRequest", filterActionsWrapper);
*/
wrapEpic = (name, epicWrapper) => {
this.epicWrappers[name] = epicWrapper;
};
/**
* It can be used to execute a function once a redux store has been created.
* @method
* @param {Function} onCreated The function which will be called when redux store is created
* @returns {Function} Unsubscribe function
* @example
* externalCodeSetup.reduxApi.addOnStoreCreateListener((props) => {
* //Redux store has been created!
* //Do something here..
* })
*/
addOnStoreCreateListener = onCreated => {
this.storeCreateListeners.push(onCreated);
return () => {
const index = this.storeCreateListeners.indexOf(onCreated);
if (index !== -1) {
this.storeCreateListeners.splice(index, 1);
}
};
};
/**
* You can use it to add a new middleware to store initialization
* @method
* @param {ReduxMiddleware} middleware
* @example
* import thunk from 'redux-thunk';
* ...
* externalCodeSetup.reduxApi.addMiddleware(thunk)
*/
addMiddleware = middleware => {
this.newMiddlewares.push(middleware);
};
/**
* You can use this to set custom persistor configuration.
* It will replace the default configuration persistor from the app.
* Persist is used to store Redux state on disk so that it can be available after reopening the app without fetching data again.
* You can refer to this link for more information: {@link https://github.com/rt2zz/redux-persist}
* @method
* @param {Object} config - App persistor config
* @example <caption> Default configuration from app </caption>
*
* ...
*
* import AsyncStorage from "@react-native-community/async-storage";
*
* export const applyCustomCode = externalCodeSetup => {
* const persistConfig = {
* storage: AsyncStorage, // where to store
* whitelist: [ // names of reducers to store
* "auth",
* "activities",
* "blog",
* "messages",
* "forums",
* "menuSettings",
* "user",
* "sites",
* "settings",
* "blockData",
* "blockLoadableDataCache",
* "activitiesCache",
* "usersCache",
* "topicsCache",
* "forumsCache",
* "coursesCache",
* "groupsCache",
* "messagesCache",
* "blogCache",
* "socialGroups",
* "members",
* "localAuthentication",
* "urls",
* "inAppPurchases",
* "singleCourse",
* "emailInvites"
* ],
* transforms: [reducerTransform, transformSerialiseImmutable],
* debounce: 300,
* key: "v5"
* };
*
* externalCodeSetup.reduxApi.overidePersistorConfig(persistConfig);
* }
*/
overidePersistorConfig = config => {
this.customPersistorConfig = config;
};
/**
* You can use this to change config for redux persistor.
* For example, adding a new reducer to the whitelist.
* @method
* @param {Function} changer Function accepts the default persistor config; Or the changed persistor if `externalCodeSetup.reduxApi.overidePersistorConfig` is used.
* @returns {Object} PersistorConfig
* @example <caption>Add new reducer to whitelist </caption>
* externalCodeSetup.reduxApi.addPersistorConfigChanger(props => {
* const changedPersistor = {
* ...props,
* whitelist: [
* ...props.whitelist,
* "customReducer"
* ]
* };
* return changedPersistor
* })
*/
addPersistorConfigChanger = changer => {
this.persitorConfigChangers.push(changer);
};
/**
* If set to `false`, redux data won't be saved to disk.
* @method
* @example
* externalCodeSetup.reduxApi.setIsDataPersistingEnabled(false)
*/
setIsDataPersistingEnabled = isEnabled => {
this.isDataPersistingEnabled = isEnabled;
};
}
Source