diff --git a/MaharaMobile/App.tsx b/MaharaMobile/App.tsx index ce5e8d0355825e5b5a040b83ce2098d515096a62..c9f37dd90978dda590b8f288eca58aaf10eef119 100644 --- a/MaharaMobile/App.tsx +++ b/MaharaMobile/App.tsx @@ -1,27 +1,9 @@ import React from 'react'; -import { Platform } from 'react-native'; import { Provider } from 'react-redux'; -import { createAppContainer, createSwitchNavigator } from 'react-navigation'; -import { createStackNavigator } from 'react-navigation-stack'; -import { createBottomTabNavigator } from 'react-navigation-tabs'; -import { FontAwesomeIcon } from '@fortawesome/react-native-fontawesome'; -import { - faUser, - faPlusCircle, - faHistory -} from '@fortawesome/free-solid-svg-icons'; -import { createMaterialBottomTabNavigator } from 'react-navigation-material-bottom-tabs'; import { I18nProvider } from '@lingui/react'; -import styles from './assets/styles/variables'; import configureStore from './store/store'; -import SiteCheckScreen from './screens/SiteCheckScreen/SiteCheckScreen'; -import LoginScreen from './screens/LoginScreen/LoginScreen'; -import ProfileScreen from './screens/ProfileScreen/ProfileScreen'; -import PendingScreen from './screens/PendingScreen/PendingScreen'; -import AddScreen from './screens/AddScreen/AddScreen'; -import AddFileScreen from './screens/AddFileScreen/AddFileScreen'; -import DetailsScreen from './screens/DetailsScreen/DetailsScreen'; +import AppNavigator from './navigations/app-navigator'; const App = () => { const store = configureStore(); @@ -30,91 +12,10 @@ const App = () => { return ( <Provider store={store}> <I18nProvider language="en"> - <NavigationContainer /> + <AppNavigator /> </I18nProvider> </Provider> ); }; -const NavigationContainer = () => { - const AddItemsNavigator = createStackNavigator({ - Add: AddScreen, - AddFile: AddFileScreen, - }); - - const PendingItemsNavigator = createStackNavigator({ - Pending: PendingScreen, - Details: { - screen: DetailsScreen, - navigationOptions: { - headerTitle: 'Back to Pending Items' - } - } - }); - - const tabScreenConfig = { - Profile: { - screen: ProfileScreen, - navigationOptions: { - tabBarIcon: () => ( - <FontAwesomeIcon icon={faUser} color={styles.colors.light} /> - ), - tabBarAccessibilityLabel: 'Profile page' - } - }, - Add: { - screen: AddItemsNavigator, - navigationOptions: { - tabBarLabel: 'Add', - tabBarIcon: () => ( - <FontAwesomeIcon icon={faPlusCircle} color={styles.colors.light} /> - ), - tabBarAccessibilityLabel: 'Add item to Mahara' - } - }, - PendingScreen: { - screen: PendingItemsNavigator, - navigationOptions: { - tabBarLabel: 'Pending ', - tabBarIcon: () => ( - <FontAwesomeIcon icon={faHistory} color={styles.colors.light} /> - ), - tabBarAccessibilityLabel: 'Pending uploads page' - } - } - }; - - // Check the OS system for correct styles - const AppTabNavigator = Platform.OS === 'android' - ? createMaterialBottomTabNavigator(tabScreenConfig, { - activeColor: styles.colors.light, - // shifting: true, - barStyle: { - backgroundColor: styles.colors.secondary, - }, - }) - : createBottomTabNavigator(tabScreenConfig, { - tabBarOptions: { - activeBackgroundColor: styles.colors.secondary, - activeTintColor: styles.colors.light, - }, - }); - - // Navigator with only LoginScreen - const AuthNavigator = createStackNavigator({ - Auth: SiteCheckScreen, - Login: LoginScreen - }); - - // Main navigator, with route to AppNavigator once authenticated - const MainNavigator = createSwitchNavigator({ - Auth: AuthNavigator, - App: AppTabNavigator - }); - const Navigation = createAppContainer(MainNavigator); - - - return <Navigation />; -}; - export default App; diff --git a/MaharaMobile/actions/actions.ts b/MaharaMobile/actions/actions.ts index 29fa6894bb7ae921a2af545589abd1696f06a1a0..0630bd213c6bf798991ba01543b0fe3eaea136c4 100644 --- a/MaharaMobile/actions/actions.ts +++ b/MaharaMobile/actions/actions.ts @@ -1,55 +1,173 @@ -import { MaharaPendingFile, PendingJournalEntry, RequestErrorPayload } from '../models/models'; -import { UPDATE_SERVER_URL, UPDATE_USERNAME, UPDATE_USER_TAGS, UPDATE_USER_BLOGS, UPDATE_USER_FOLDERS, ADD_TOKEN, ADD_UPLOAD_FILE, ADD_UPLOAD_JOURNAL_ENTRY, REMOVE_UPLOAD_FILE, REMOVE_UPLOAD_JOURNAL_ENTRY } from '../utils/constants'; +import AsyncStorage from '@react-native-community/async-storage'; +import { MaharaPendingFile, PendingJournalEntry, RequestErrorPayload, UserBlog, UserFolder } from '../models/models'; +import { + UPDATE_USERNAME, + UPDATE_USER_TAGS, + UPDATE_USER_BLOGS, + UPDATE_USER_FOLDERS, + ADD_TOKEN, + ADD_UPLOAD_FILE, + ADD_UPLOAD_JOURNAL_ENTRY, + REMOVE_UPLOAD_FILE, + REMOVE_UPLOAD_JOURNAL_ENTRY, + CLEAR_USER_TAGS, + CLEAR_LOGIN_INFO, + CLEAR_UPLOAD_FILES, + CLEAR_UPLOAD_J_ENTRIES, + CLEAR_USER_BLOGS, + CLEAR_USER_FOLDERS, + UPDATE_J_ENTRIES_ON_LOGIN, + UPDATE_UPLOAD_FILES_ON_LOGIN, + UPDATE_LOGIN_TYPES, + UPDATE_URL, + UPDATE_PROFILE_ICON, + UPDATE_GUEST_STATUS +} from '../utils/constants'; // action creators - functions that create actions -export function loginTypes(url: string, response: any) { - const tokenLogin = response.logintypes.includes('manual') ? true : false; - const localLogin = response.logintypes.includes('basic') ? true : false; - const ssoLogin = response.logintypes.includes('sso') ? true : false; +// userTagsReducer +export function updateUserTags(tags: any) { + AsyncStorage.setItem('userTags', JSON.stringify(tags)); + return { type: UPDATE_USER_TAGS, userTags: tags }; +} + +export function clearUserTags() { + return { type: CLEAR_USER_TAGS }; +} + +// loginInfoReducer +export function updateGuestStatus(isGuest: boolean) { + return { type: UPDATE_GUEST_STATUS, isGuest }; +} + +export function addToken(token: string) { + AsyncStorage.setItem('userToken', token); + return { type: ADD_TOKEN, token }; +} + +export function updateUserName(username: any) { + AsyncStorage.setItem('username', username); + return { type: UPDATE_USERNAME, userName: username }; +} + +export function updateUrl(url: string) { + AsyncStorage.setItem('url', url); return { - type: UPDATE_SERVER_URL, - url: url, - tokenLogin: tokenLogin, - localLogin: localLogin, - ssoLogin: ssoLogin + type: UPDATE_URL, + url: url + }; +} + +export function updateProfilePic(filepath: string) { + AsyncStorage.setItem('profileIcon', filepath); + return { + type: UPDATE_PROFILE_ICON, + profileIcon: filepath } } -export function addToken(token: string) { - return { type: ADD_TOKEN, token } +/** + * Update stored boolean login types + * - response retrieved when users login the first time + * - localL, tokenL, and ssoL are retrieved from AsyncStorage + */ +export function updateLoginTypes( + response: any, + localL = false, + tokenL = false, + ssoL = false +) { + let tokenLogin; + let localLogin; + let ssoLogin; + if (response) { + tokenLogin = response.logintypes.includes('manual'); + localLogin = response.logintypes.includes('basic'); + ssoLogin = response.logintypes.includes('sso'); + } else { + tokenLogin = tokenL; + localLogin = localL; + ssoLogin = ssoL; + } + + AsyncStorage.setItem('tokenLogin', JSON.stringify(tokenLogin)); + AsyncStorage.setItem('localLogin', JSON.stringify(localLogin)); + AsyncStorage.setItem('ssoLogin', JSON.stringify(ssoLogin)); + + return { + type: UPDATE_LOGIN_TYPES, + tokenLogin, + localLogin, + ssoLogin + } +} + +export function clearLoginInfo() { + return { type: CLEAR_LOGIN_INFO }; } +// uploadFilesReducer export function addFileToUploadList(file: MaharaPendingFile) { return { type: ADD_UPLOAD_FILE, file } } +export function removeUploadFile(id: string) { + return { type: REMOVE_UPLOAD_FILE, id } +} + +export function clearUploadFiles() { + return { type: CLEAR_UPLOAD_FILES }; +} + +export function updateUploadFilesOnLogin( + token: string, + urlDomain: string, + userFolders: Array<UserFolder>, +) { + return { type: UPDATE_UPLOAD_FILES_ON_LOGIN, token, urlDomain, userFolders }; +} + +// uploadJEntriesReducer export function addJournalEntryToUploadList(journalEntry: PendingJournalEntry) { - return { type: ADD_UPLOAD_JOURNAL_ENTRY, journalEntry } + return { type: ADD_UPLOAD_JOURNAL_ENTRY, journalEntry }; } -export function updateUserName(json: any) { - return { type: UPDATE_USERNAME, userName: json.userprofile.myname } +export function removeUploadJEntry(id: string) { + return { type: REMOVE_UPLOAD_JOURNAL_ENTRY, id }; } -export function updateUserTags(json: any) { - return { type: UPDATE_USER_TAGS, userTags: json.tags.tags } +export function clearUploadJEntires() { + return { type: CLEAR_UPLOAD_J_ENTRIES }; } -export function updateUserBlogs(json: any) { - return { type: UPDATE_USER_BLOGS, userBlogs: json.blogs.blogs } +export function updateJEntriesOnLogin( + token: string, + urlDomain: string, + userBlogs: Array<UserBlog>, +) { + return { type: UPDATE_J_ENTRIES_ON_LOGIN, token, urlDomain, userBlogs }; } -export function updateUserFolders(json: any) { - return { type: UPDATE_USER_FOLDERS, userFolders: json.folders.folders } +// userArtefactsReducer +export function updateUserBlogs(blogs: any) { + AsyncStorage.setItem('userBlogs', JSON.stringify(blogs)); + return { type: UPDATE_USER_BLOGS, userBlogs: blogs }; } -export function removeUploadFile(id: string) { - return { type: REMOVE_UPLOAD_FILE, id } +export function updateUserFolders(folders: any) { + AsyncStorage.setItem('userFolders', JSON.stringify(folders)); + return { type: UPDATE_USER_FOLDERS, userFolders: folders }; } -export function removeUploadJEntry(id: string) { - return { type: REMOVE_UPLOAD_JOURNAL_ENTRY, id } +export function clearUserBlogs() { + AsyncStorage.removeItem('userBlogs'); + return { type: CLEAR_USER_BLOGS }; +} + +export function clearUserFolders() { + AsyncStorage.removeItem('userFolders'); + return { type: CLEAR_USER_FOLDERS }; } export class RequestError extends Error { @@ -85,7 +203,7 @@ const postJSON = (url: string, body: any) => { return requestJSON(url, { method: 'POST', body: body, - }) + }); }; const requestJSON = async (url: any, config: any) => { @@ -102,7 +220,7 @@ const requestJSON = async (url: any, config: any) => { } catch (error) { throw RequestError.createFromError(error); } -} +}; export function checkLoginTypes(url: string) { const serverUrl = url + 'module/mobileapi/json/info.php'; @@ -110,22 +228,19 @@ export function checkLoginTypes(url: string) { return async function (dispatch: any) { try { // TODO: dispatch loading state for spinner - const result: any = await getJSON(serverUrl); - // check that there is a mahara version, and therefore a Mahara instance if (!result.maharaversion) { throw new Error('This is not a Mahara site'); } - // check that webservices is enabled on the Mahara instance if (!result.wsenabled) { throw new Error('Webservices is not enabled.'); } - - dispatch(loginTypes(url, result)); + dispatch(updateLoginTypes(result)); + dispatch(updateUrl(url)); } catch (error) { throw error; } } -} \ No newline at end of file +} diff --git a/MaharaMobile/assets/styles/headings.ts b/MaharaMobile/assets/styles/headings.ts index 6018a5703a8c74e7f7db055fb2556bdf3dce29df..70d038f7f6e00b08f01d6f9b0c134a336c8db807 100644 --- a/MaharaMobile/assets/styles/headings.ts +++ b/MaharaMobile/assets/styles/headings.ts @@ -1,7 +1,7 @@ import { StyleSheet } from 'react-native'; import styles from './variables'; -export const headings = StyleSheet.create({ +export const headingStyles = StyleSheet.create({ mainHeading: { fontSize: styles.font.lg, color: styles.colors.secondary, diff --git a/MaharaMobile/components/LoginType/LoginType.tsx b/MaharaMobile/components/LoginType/LoginType.tsx index 600425ba8690cd057a9bb7786dccfbbac941e09d..582dceb70091ced9d968ddbb74b88dc0aef8a079 100644 --- a/MaharaMobile/components/LoginType/LoginType.tsx +++ b/MaharaMobile/components/LoginType/LoginType.tsx @@ -3,9 +3,9 @@ import { Text, View, TouchableOpacity, TextInput } from 'react-native'; import { Trans, t } from '@lingui/macro'; import { I18n } from "@lingui/react"; import styles from './LoginType.style'; -import { headings } from '../../assets/styles/headings'; import { forms } from '../../assets/styles/forms'; import { buttons } from '../../assets/styles/buttons'; +import { headingStyles } from '../../assets/styles/headings'; type Props = { url: string; @@ -20,7 +20,8 @@ type Props = { serverPing: boolean; isInputHidden: boolean; enterUrlWarning: boolean; - skip: () => void; + navigation: any; + onSkip: () => void; }; export default function LoginType(props: Props) { @@ -28,16 +29,16 @@ export default function LoginType(props: Props) { <View style={styles.view}> {!props.isInputHidden ? ( <View> - <Text style={headings.subHeading1}> + <Text style={headingStyles.subHeading1}> <Trans>What is the address of your Mahara?</Trans> </Text> <I18n> {({i18n}) => <TextInput - style={forms.textInput} - // placeholder={'https://yoursite.edu/'} TODO: put this back in and remove default value for go live - defaultValue={i18n._(t `https://master.dev.mahara.org/`)} - onChangeText={(url:string) => props.checkUrl(url)} - /> + style={forms.textInput} + // placeholder={'https://yoursite.edu/'} TODO: put this back in and remove default value for go live + defaultValue={i18n._(t`https://master.dev.mahara.org/`)} + onChangeText={(url: string) => props.checkUrl(url)} + /> } </I18n> </View> @@ -46,7 +47,7 @@ export default function LoginType(props: Props) { {props.errorMessage ? <Text>{props.errorMessage}</Text> : null} {props.serverPing && props.isInputHidden ? ( <View> - <Text style={[headings.subHeading2, styles.url]}>{props.url}</Text> + <Text style={[headingStyles.subHeading2, styles.url]}>{props.url}</Text> <TouchableOpacity onPress={() => props.resetForm()}> <Text style={[buttons.md, styles.buttons]}> <Trans>Enter a different URL</Trans> @@ -59,13 +60,13 @@ export default function LoginType(props: Props) { <TouchableOpacity onPress={() => props.checkServer()}> <Text style={[buttons.md, styles.buttons]}><Trans>Next</Trans></Text> </TouchableOpacity> - <TouchableOpacity onPress={props.skip}> + <TouchableOpacity onPress={() => props.onSkip()}> <Text style={[buttons.md, styles.buttons]}><Trans>Skip</Trans></Text> </TouchableOpacity> </View> ) : null} {props.serverPing ? ( - <Text style={headings.mainHeading}><Trans>Select login type</Trans></Text> + <Text style={headingStyles.mainHeading}><Trans>Select login type</Trans></Text> ) : null} {props.serverPing && props.tokenLogin ? ( <TouchableOpacity onPress={() => props.setLoginType('token')}> diff --git a/MaharaMobile/components/MediumButton/MediumButton.style.ts b/MaharaMobile/components/MediumButton/MediumButton.style.ts new file mode 100644 index 0000000000000000000000000000000000000000..dffef8c1936a3390859d069a70bc10c490a47ee3 --- /dev/null +++ b/MaharaMobile/components/MediumButton/MediumButton.style.ts @@ -0,0 +1,10 @@ +import { StyleSheet } from 'react-native'; +import styles from '../../assets/styles/variables'; + +const mdButtonStyles = StyleSheet.create({ + buttons: { + marginBottom: styles.padding.md + } +}); + +export default mdButtonStyles; diff --git a/MaharaMobile/components/MediumButton/MediumButton.tsx b/MaharaMobile/components/MediumButton/MediumButton.tsx new file mode 100644 index 0000000000000000000000000000000000000000..c4bc0db496bbc69fe5ff15ce7b6fbd692717ee96 --- /dev/null +++ b/MaharaMobile/components/MediumButton/MediumButton.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { TouchableOpacity } from 'react-native'; +import { Text } from 'react-native'; +import { Trans, I18n } from '@lingui/react'; +import { t } from '@lingui/macro'; +import mdButtonStyles from './MediumButton.style'; +import { buttons } from '../../assets/styles/buttons'; + +type Props = { + onPress: () => void; + title: string; +}; + +const MediumButton = (props: Props) => ( + <TouchableOpacity onPress={props.onPress}> + <I18n> + {({ i18n }) => ( + <Text style={[buttons.md, mdButtonStyles.buttons]}> + {i18n._(t`${props.title}`)} + </Text> + )} + </I18n> + </TouchableOpacity> +); + +export default MediumButton; diff --git a/MaharaMobile/components/Profile/Profile.tsx b/MaharaMobile/components/Profile/Profile.tsx index f474be37a8970233226ece12126b672a8ae6da21..5270a1e8bc57c8002c14ca9587d4fbee77bf9e9b 100644 --- a/MaharaMobile/components/Profile/Profile.tsx +++ b/MaharaMobile/components/Profile/Profile.tsx @@ -1,23 +1,25 @@ -import React, { Component } from 'react'; +import React from 'react'; import { Text, View, Image } from 'react-native'; import styles from './Profile.style'; type Props = { name: string; profileIcon: string; -} +}; -export default class Profile extends Component<Props> { - render() { - const image: any = require('../../assets/images/no_userphoto.png'); - - return ( - <View style={styles.view}> - <View style={styles.container}> - <Image source={this.props.profileIcon ? { uri: this.props.profileIcon } : image} style={styles.image} /> - </View> - <Text style={styles.name}>Hi {this.props.name}</Text> +const Profile = (props: Props) => { + const image: any = require('../../assets/images/no_userphoto.png'); + return ( + <View style={styles.view}> + <View style={styles.imageContainer}> + <Image + source={props.profileIcon ? { uri: props.profileIcon } : image} + style={styles.image} + /> + <Text style={styles.name}>Hi {props.name ? props.name : 'Guest'}</Text> </View> - ) - } -} + </View> + ); +}; + +export default Profile; diff --git a/MaharaMobile/components/TokenInput/TokenInput.tsx b/MaharaMobile/components/TokenInput/TokenInput.tsx index 9f0715fa2a1b01c4ea544653fd6c5df228669cc6..224596e83cdcee211d989ea564c41a09fa1544d8 100644 --- a/MaharaMobile/components/TokenInput/TokenInput.tsx +++ b/MaharaMobile/components/TokenInput/TokenInput.tsx @@ -1,29 +1,32 @@ +/* eslint-disable prettier/prettier */ import React from 'react'; -import { Text, TextInput, View, TouchableOpacity } from 'react-native'; +import { + Text, TextInput, View, TouchableOpacity +} from 'react-native'; import styles from './TokenInput.style'; import { forms } from '../../assets/styles/forms'; +import { headingStyles } from '../../assets/styles/headings'; import { buttons } from '../../assets/styles/buttons'; -import { headings } from '../../assets/styles/headings'; type Props = { - handleToken: Function; - setToken: Function; -} + onVerifyToken: Function; + onUpdateToken: Function; +}; export default function TokenInput(props: Props) { return ( <View style={styles.view}> - <Text style={headings.mainHeading}>Login via Token</Text> - <TextInput + <Text style={headingStyles.mainHeading}>Login via Token</Text> + <TextInput style={forms.textInput} - //TODO: remove default value for go live - defaultValue='c6f3d4fd4b997c96392deeb127ec983b' - onChangeText={(token) => props.setToken(token)} + // TODO: remove default value for go live + defaultValue="c6f3d4fd4b997c96392deeb127ec983b" + onChangeText={(token) => props.onUpdateToken(token)} /> - <TouchableOpacity onPress={() => props.handleToken()}> + <TouchableOpacity onPress={() => props.onVerifyToken()}> <Text style={buttons.lg}>Verify Token</Text> </TouchableOpacity> </View> - ) + ); } diff --git a/MaharaMobile/components/UploadForm/UploadForm.tsx b/MaharaMobile/components/UploadForm/UploadForm.tsx index 635de759a094ece3775e800c5212818ab10c3a0c..4356322a783726913d01e2e6cd8999d1241b2d79 100644 --- a/MaharaMobile/components/UploadForm/UploadForm.tsx +++ b/MaharaMobile/components/UploadForm/UploadForm.tsx @@ -143,7 +143,7 @@ const UploadForm = (props: Props) => { // upon successful upload, remove the AddFile screen from the navigation stack props.navigation.dispatch(StackActions.popToTop()); // then take user to PendingScreen - props.navigation.navigate({routeName: 'PendingScreen', params: { fileType: type }}); + props.navigation.navigate({routeName: 'Pending', params: { fileType: type }}); }; return ( diff --git a/MaharaMobile/components/UploadItem/UploadItem.style.ts b/MaharaMobile/components/UploadItem/UploadItem.style.ts index f8a1668963083b34052c67e9712b75656eb772b4..a9a8727715642f907ed55bed889be135ac27ff79 100644 --- a/MaharaMobile/components/UploadItem/UploadItem.style.ts +++ b/MaharaMobile/components/UploadItem/UploadItem.style.ts @@ -7,7 +7,7 @@ const uploadItemStyles = StyleSheet.create({ flexDirection: 'row', padding: 3, marginVertical: 3, - height: 120, + height: 110, }, pendingCard: { flexDirection: 'row', diff --git a/MaharaMobile/navigations/app-navigator.tsx b/MaharaMobile/navigations/app-navigator.tsx new file mode 100644 index 0000000000000000000000000000000000000000..d1b523c15a58f0795c723020ed70007bb4363e1a --- /dev/null +++ b/MaharaMobile/navigations/app-navigator.tsx @@ -0,0 +1,110 @@ +import React from 'react'; +import { createStackNavigator } from 'react-navigation-stack'; +import { + faUser, + faPlusCircle, + faHistory +} from '@fortawesome/free-solid-svg-icons'; +import { createMaterialBottomTabNavigator } from 'react-navigation-material-bottom-tabs'; +import { createBottomTabNavigator } from 'react-navigation-tabs'; +import { Platform } from 'react-native'; +import {createSwitchNavigator, createAppContainer } from 'react-navigation'; +import { FontAwesomeIcon } from '@fortawesome/react-native-fontawesome'; +import AddScreen from '../screens/AddScreen/AddScreen'; +import AddFileScreen from '../screens/AddFileScreen/AddFileScreen'; +import PendingScreen from '../screens/PendingScreen/PendingScreen'; +import DetailsScreen from '../screens/DetailsScreen/DetailsScreen'; +import ProfileScreen from '../screens/ProfileScreen/ProfileScreen'; +import styles from '../assets/styles/variables'; +import SiteCheckScreen from '../screens/SiteCheckScreen/SiteCheckScreen'; +import LoginScreen from '../screens/LoginScreen/LoginScreen'; +import AuthLoadingScreen from '../screens/AuthLoadingScreen/AuthLoadingScreen'; + +const AppNavigator = () => { + const AddItemsNavigator = createStackNavigator({ + AddItems: AddScreen, + AddFile: AddFileScreen + }); + + const PendingItemsNavigator = createStackNavigator({ + Pending: PendingScreen, + Details: { + screen: DetailsScreen, + navigationOptions: { + headerTitle: 'Back to Pending Items' + } + } + }); + + const tabScreenConfig = { + ProfileTab: { + screen: ProfileScreen, + navigationOptions: { + tabBarLabel: 'Profile', + tabBarIcon: () => ( + <FontAwesomeIcon icon={faUser} color={styles.colors.light} /> + ), + tabBarAccessibilityLabel: 'Profile page' + } + }, + AddItemsTab: { + screen: AddItemsNavigator, + navigationOptions: { + tabBarLabel: 'Add', + tabBarIcon: () => ( + <FontAwesomeIcon icon={faPlusCircle} color={styles.colors.light} /> + ), + tabBarAccessibilityLabel: 'Add item to Mahara' + } + }, + PendingTab: { + screen: PendingItemsNavigator, + navigationOptions: { + tabBarLabel: 'Pending ', + tabBarIcon: () => ( + <FontAwesomeIcon icon={faHistory} color={styles.colors.light} /> + ), + tabBarAccessibilityLabel: 'Pending uploads page' + } + } + }; + + const androidTabConfig = createMaterialBottomTabNavigator(tabScreenConfig, { + activeColor: styles.colors.light, + barStyle: { + backgroundColor: styles.colors.secondary + } + }); + + const iOSTabConfig = createBottomTabNavigator(tabScreenConfig, { + tabBarOptions: { + activeBackgroundColor: styles.colors.secondary, + activeTintColor: styles.colors.light + } + }); + + const AppTabNavigator = Platform.OS === 'android' ? androidTabConfig : iOSTabConfig; + + // Navigator with only LoginScreen + const AuthNavigator = createStackNavigator({ + SiteCheck: SiteCheckScreen, + Login: LoginScreen + }); + + // Main navigator, with route to AppNavigator once authenticated + const MainNavigator = createSwitchNavigator( + { + AuthLoading: AuthLoadingScreen, + App: AppTabNavigator, + Auth: AuthNavigator + }, + { + initialRouteName: 'AuthLoading' + } + ); + + const Navigation = createAppContainer(MainNavigator); + return <Navigation />; +}; + +export default AppNavigator; diff --git a/MaharaMobile/reducers/loginInfoReducer.ts b/MaharaMobile/reducers/loginInfoReducer.ts index c217334a7b60b530a34b625dbf108c0e6a413b39..264aececd967a27bfa9922e0a5cfd8daf2bcc00c 100644 --- a/MaharaMobile/reducers/loginInfoReducer.ts +++ b/MaharaMobile/reducers/loginInfoReducer.ts @@ -2,13 +2,20 @@ import { UPDATE_SERVER_URL, ADD_TOKEN, UPDATE_USERNAME, + CLEAR_LOGIN_INFO, + UPDATE_LOGIN_TYPES, + UPDATE_URL, + UPDATE_PROFILE_ICON, + UPDATE_GUEST_STATUS } from '../utils/constants'; -import { RootState } from './reducers'; +import { RootState } from './rootReducer'; type LoginInfoState = { url: string; token: string; userName: string; + isGuest: boolean; + profileIcon: string; // available login methods tokenLogin: boolean; ssoLogin: boolean; @@ -22,27 +29,53 @@ const initialState: LoginInfoState = { localLogin: false, token: '', userName: '', + isGuest: false, + profileIcon: '' }; -export const loginInfoReducer = (state = initialState, action: any) => { +export const loginInfoReducer = ( + state = initialState, + action: any +): LoginInfoState => { switch (action.type) { case UPDATE_SERVER_URL: return { ...state, - url: action.url, - tokenLogin: action.tokenLogin, - ssoLogin: action.ssoLogin, - localLogin: action.localLogin, + url: action.url }; case ADD_TOKEN: return { ...state, - token: action.token, + token: action.token }; case UPDATE_USERNAME: return { ...state, - userName: action.userName, + userName: action.userName + }; + case CLEAR_LOGIN_INFO: + return initialState; + case UPDATE_LOGIN_TYPES: + return { + ...state, + localLogin: action.localLogin, + ssoLogin: action.ssoLogin, + tokenLogin: action.tokenLogin + }; + case UPDATE_URL: + return { + ...state, + url: action.url + }; + case UPDATE_PROFILE_ICON: + return { + ...state, + profileIcon: action.profileIcon + }; + case UPDATE_GUEST_STATUS: + return { + ...state, + isGuest: action.isGuest }; default: return state; @@ -52,8 +85,10 @@ export const loginInfoReducer = (state = initialState, action: any) => { // Selector export const selectUrl = (state: RootState) => state.domainData.loginInfo.url; export const selectTokenLogin = (state: RootState) => state.domainData.loginInfo.tokenLogin; -export const selectSsoLogin = (state: RootState) => state.domainData.loginInfo.ssoLogin; -export const selectLocalLogin = (state: RootState) => state.domainData.loginInfo.localLogin; -export const selectToken = (state: RootState) => state.domainData.loginInfo.token; -export const selectUserName = (state: RootState) => state.domainData.loginInfo.userName; -export const selectLoginState = (state: RootState) => (state.domainData.loginInfo.userName !=='' ? true : false); +export const selectSsoLogin = (state: RootState) => state.domainData.loginInfo.ssoLogin; +export const selectLocalLogin = (state: RootState) => state.domainData.loginInfo.localLogin; +export const selectToken = (state: RootState) => state.domainData.loginInfo.token; +export const selectUserName = (state: RootState) => state.domainData.loginInfo.userName; +export const selectAllLoginInfo = (state: RootState) => state.domainData.loginInfo; +export const selectProfileIcon = (state: RootState) => state.domainData.loginInfo.profileIcon; +export const selectIsGuestStatus = (state: RootState) => state.domainData.loginInfo.isGuest; diff --git a/MaharaMobile/reducers/rootReducer.ts b/MaharaMobile/reducers/rootReducer.ts index f429d2068dc501616b3b88508d74ad1b076937a5..f2a37e21c1655050e3c4489a6cdd4bce8d88463e 100644 --- a/MaharaMobile/reducers/rootReducer.ts +++ b/MaharaMobile/reducers/rootReducer.ts @@ -11,7 +11,7 @@ export const rootReducer = combineReducers({ loginInfo: loginInfoReducer, userTags: userTagsReducer, userFolders: userFoldersReducer, - userBlogs: userBlogsReducer, + userBlogs: userBlogsReducer }), appState: combineReducers({ uploadFiles: uploadFilesReducer, @@ -19,4 +19,4 @@ export const rootReducer = combineReducers({ }) }); -export type RootState = ReturnType<typeof rootReducer>; \ No newline at end of file +export type RootState = ReturnType<typeof rootReducer>; diff --git a/MaharaMobile/reducers/uploadFilesReducer.ts b/MaharaMobile/reducers/uploadFilesReducer.ts index 55c3dbdaba00384b9a007cd4a4d1502f4eacfb32..278910bb6e717a6519fe2bcfe6e18bc9f6b8c8da 100644 --- a/MaharaMobile/reducers/uploadFilesReducer.ts +++ b/MaharaMobile/reducers/uploadFilesReducer.ts @@ -1,6 +1,13 @@ -import { MaharaPendingFile } from '../models/models'; -import { RootState } from './reducers'; -import { ADD_UPLOAD_FILE, REMOVE_UPLOAD_FILE } from '../utils/constants'; +import AsyncStorage from '@react-native-community/async-storage'; +import {MaharaPendingFile, UserFolder} from '../models/models'; +import { RootState } from './rootReducer'; +import { + ADD_UPLOAD_FILE, + REMOVE_UPLOAD_FILE, + CLEAR_UPLOAD_FILES, + UPDATE_UPLOAD_FILES_ON_LOGIN +} from '../utils/constants'; +import { arrayToObject } from '../utils/authHelperFunctions'; type UploadFilesState = { uploadFiles: Record<string, MaharaPendingFile>; @@ -9,48 +16,88 @@ type UploadFilesState = { const initialState: UploadFilesState = { uploadFiles: {}, - uploadFilesIds: [], + uploadFilesIds: [] }; // Helper functions +const getFiles = (ids: string[], arr: any) => ids.map((id: string) => arr[id]); + +const updateAsyncStorageUploadFiles = (uploadFiles: MaharaPendingFile[]) => { + AsyncStorage.setItem('uploadFiles', JSON.stringify(uploadFiles)); +}; + const addFileToUploadList = ( state: UploadFilesState, - file: MaharaPendingFile, + file: MaharaPendingFile ) => { - const updatedUploadFilesIds = new Set([ - ...state.uploadFilesIds, - file.id, - ]) - const updatedUploadFiles = { + const updatedFilesIdsSet = new Set([...state.uploadFilesIds, file.id]); + const updatedFiles = { ...state.uploadFiles, - [file.id]: file, + [file.id]: file }; + const updatedFileIds = Array.from(updatedFilesIdsSet); + updateAsyncStorageUploadFiles(getFiles(updatedFileIds, updatedFiles)); return { - uploadFiles: updatedUploadFiles, - uploadFilesIds: Array.from(updatedUploadFilesIds) + uploadFiles: updatedFiles, + uploadFilesIds: updatedFileIds }; }; const removeUploadFile = ( state: UploadFilesState, - id: string, + id: string ): UploadFilesState => { // Filter out given id from state const updatedFileIds = state.uploadFilesIds.filter( - (uploadFilesId: string) => uploadFilesId !== id, + (uploadFilesId: string) => uploadFilesId !== id ); // Delete the file matching id - const updatedUploadFiles = { ...state.uploadFiles }; - delete updatedUploadFiles[id]; + const updatedFiles = { ...state.uploadFiles }; + delete updatedFiles[id]; + updateAsyncStorageUploadFiles(getFiles(updatedFileIds, updatedFiles)); return { - uploadFiles: updatedUploadFiles, - uploadFilesIds: updatedFileIds, + uploadFiles: updatedFiles, + uploadFilesIds: updatedFileIds }; }; +const updateUploadFilesOnLogin = ( + state: UploadFilesState, + token: string, + urlDomain: string, + userFolders: Array<UserFolder> +): UploadFilesState => { + const uploadJEntries = { + ...state.uploadFiles + }; + + const updatedFiles: Array<MaharaPendingFile> = []; + const filesArray = Object.values(uploadJEntries); + filesArray.forEach((file: MaharaPendingFile) => { + const newFile: MaharaPendingFile = { + ...file, + maharaFormData: { + ...file.maharaFormData, + wstoken: token, + foldername: userFolders[0].title + }, + url: urlDomain + file.url + }; + updatedFiles.push(newFile); + }); + + const updatedFilesObj = arrayToObject(updatedFiles); + const newState: UploadFilesState = { + ...state, + uploadFiles: updatedFilesObj + }; + updateAsyncStorageUploadFiles(updatedFiles); + return newState; +}; + // REDUCER export const uploadFilesReducer = (state = initialState, action: any) => { switch (action.type) { @@ -58,6 +105,15 @@ export const uploadFilesReducer = (state = initialState, action: any) => { return addFileToUploadList(state, action.file); case REMOVE_UPLOAD_FILE: return removeUploadFile(state, action.id); + case CLEAR_UPLOAD_FILES: + return initialState; + case UPDATE_UPLOAD_FILES_ON_LOGIN: + return updateUploadFilesOnLogin( + state, + action.token, + action.urlDomain, + action.userFolders + ); default: return state; } @@ -67,19 +123,21 @@ const uploadFilesState = (state: RootState) => state.appState.uploadFiles; // Selectors export const selectAllUploadFiles = ( - state: RootState, + state: RootState ): Array<MaharaPendingFile> => { const { uploadFiles } = uploadFilesState(state); const { uploadFilesIds } = uploadFilesState(state); - const files = uploadFilesIds.map((id: string) => uploadFiles[id]); - return files; + return getFiles(uploadFilesIds, uploadFiles); }; export const selectAllUploadFilesIds = (state: RootState) => [ - ...state.appState.uploadFiles.uploadFilesIds, + ...state.appState.uploadFiles.uploadFilesIds ]; -export const selectUploadFileById = (state: RootState, { id }: { id: string }): MaharaPendingFile => { +export const selectUploadFileById = ( + state: RootState, + { id }: {id: string} +): MaharaPendingFile => { const { uploadFiles } = uploadFilesState(state); return uploadFiles[id]; }; diff --git a/MaharaMobile/reducers/uploadJEntriesReducer.ts b/MaharaMobile/reducers/uploadJEntriesReducer.ts index f23301813f6b3cd5bb99734dc965b2b280de22d6..5178916b4f565ba01bcc0f1ead0daa209c2a3cac 100644 --- a/MaharaMobile/reducers/uploadJEntriesReducer.ts +++ b/MaharaMobile/reducers/uploadJEntriesReducer.ts @@ -1,9 +1,13 @@ -import { PendingJournalEntry } from '../models/models'; -import { RootState } from './reducers'; +import { PendingJournalEntry, UserBlog, UserFolder, JournalEntry } from '../models/models'; +import { RootState } from './rootReducer'; import { ADD_UPLOAD_JOURNAL_ENTRY, REMOVE_UPLOAD_JOURNAL_ENTRY, + CLEAR_UPLOAD_J_ENTRIES, + UPDATE_J_ENTRIES_ON_LOGIN } from '../utils/constants'; +import { arrayToObject } from '../utils/authHelperFunctions'; +import AsyncStorage from '@react-native-community/async-storage'; type UploadJEntriesState = { uploadJEntries: Record<string, PendingJournalEntry>; @@ -12,30 +16,35 @@ type UploadJEntriesState = { const initialState: UploadJEntriesState = { uploadJEntries: {}, - uploadJEntriesIds: [], + uploadJEntriesIds: [] +}; + +// Helper functions +const getJEntries = (ids: string[], arr: any) => ids.map((id: string) => arr[id]); + +const updateAsyncStorageJEntries = (jEntries: PendingJournalEntry[]) => { + AsyncStorage.setItem('uploadJEntries', JSON.stringify(jEntries)); }; -// HELPER FUNCTIONS const addJEntryToUploadList = ( state: UploadJEntriesState, - journalEntry: PendingJournalEntry, + journalEntry: PendingJournalEntry ) => { - const uploadJEntries = { + const updatedJEntries = { ...state.uploadJEntries, - [journalEntry.id]: journalEntry, + [journalEntry.id]: journalEntry }; - const uploadJEntriesIdsSet = new Set([ + const updatedJEntriesIdsSet = new Set([ ...state.uploadJEntriesIds, - journalEntry.id, + journalEntry.id ]); - const uploadJEntriesIds = Array.from(uploadJEntriesIdsSet) - const newState: UploadJEntriesState = { - uploadJEntries, - uploadJEntriesIds + const updatedJEntriesIds = Array.from(updatedJEntriesIdsSet); + updateAsyncStorageJEntries(getJEntries(updatedJEntriesIds, updatedJEntries)); + return { + uploadJEntries: updatedJEntries, + uploadJEntriesIds: updatedJEntriesIds }; - - return newState; }; /** @@ -43,19 +52,55 @@ const addJEntryToUploadList = ( */ const removeUploadJEntry = ( state: UploadJEntriesState, - id: string, + id: string ): UploadJEntriesState => { const updatedJEntriesIds = state.uploadJEntriesIds.filter( - (uploadJEntriesId: string) => uploadJEntriesId !== id, + (uploadJEntriesId: string) => uploadJEntriesId !== id ); const updatedJEntries = { ...state.uploadJEntries }; delete updatedJEntries[id]; + updateAsyncStorageJEntries(getJEntries(updatedJEntriesIds, updatedJEntries)); return { uploadJEntries: updatedJEntries, - uploadJEntriesIds: updatedJEntriesIds, + uploadJEntriesIds: updatedJEntriesIds + }; +}; + +const updateJEntriesOnLogin = ( + state: UploadJEntriesState, + token: string, + urlDomain: string, + userBlogs: Array<UserBlog> +): UploadJEntriesState => { + const uploadJEntries = { + ...state.uploadJEntries }; + + const newJournalEntries: Array<PendingJournalEntry> = []; + const journalEntriesArr = Object.values(uploadJEntries); + journalEntriesArr.forEach((pendingJEntry: PendingJournalEntry) => { + const newPendingJEntry: PendingJournalEntry = { + id: pendingJEntry.id, + journalEntry: { + ...pendingJEntry.journalEntry, + blogid: userBlogs[1].id, + wstoken: token + }, + url: urlDomain + pendingJEntry.url + }; + newJournalEntries.push(newPendingJEntry); + }); + + const updatedJEntries = arrayToObject(newJournalEntries); + + const newState: UploadJEntriesState = { + ...state, + uploadJEntries: updatedJEntries + }; + updateAsyncStorageJEntries(newJournalEntries); + return newState; }; // REDUCER @@ -65,6 +110,10 @@ export const uploadJEntriesReducer = (state = initialState, action: any) => { return addJEntryToUploadList(state, action.journalEntry); case REMOVE_UPLOAD_JOURNAL_ENTRY: return removeUploadJEntry(state, action.id); + case CLEAR_UPLOAD_J_ENTRIES: + return initialState; + case UPDATE_J_ENTRIES_ON_LOGIN: + return updateJEntriesOnLogin(state, action.token, action.urlDomain, action.userBlogs); default: return state; } @@ -74,7 +123,7 @@ export const uploadJEntriesReducer = (state = initialState, action: any) => { const uploadJEntriesState = (state: RootState) => state.appState.uploadJEntries; export const selectAllJEntries = ( - state: RootState, + state: RootState ): Array<PendingJournalEntry> => { const { uploadJEntries } = uploadJEntriesState(state); const { uploadJEntriesIds } = uploadJEntriesState(state); @@ -83,12 +132,12 @@ export const selectAllJEntries = ( }; export const selectAllJEntriesIds = (state: RootState) => [ - ...state.appState.uploadJEntries.uploadJEntriesIds, + ...state.appState.uploadJEntries.uploadJEntriesIds ]; export const selectJEntryById = ( state: RootState, - { id }: { id: string }, + { id }: { id: string } ): PendingJournalEntry => { const { uploadJEntries } = uploadJEntriesState(state); return uploadJEntries[id]; diff --git a/MaharaMobile/reducers/userArtefactsReducer.ts b/MaharaMobile/reducers/userArtefactsReducer.ts index a10b966154c728a2c6c51183e976d2107e154065..7950d5a871c8279545a1bc0a38a29e76d6c4e262 100644 --- a/MaharaMobile/reducers/userArtefactsReducer.ts +++ b/MaharaMobile/reducers/userArtefactsReducer.ts @@ -1,10 +1,15 @@ import { UserFolder, UserBlog } from '../models/models'; -import { RootState } from './reducers'; -import { UPDATE_USER_FOLDERS, UPDATE_USER_BLOGS } from '../utils/constants'; +import { RootState } from './rootReducer'; +import { + UPDATE_USER_FOLDERS, + UPDATE_USER_BLOGS, + CLEAR_USER_BLOGS, + CLEAR_USER_FOLDERS, +} from '../utils/constants'; // UserFolders type UserFoldersState = Array<UserFolder>; -const initialUserFoldersState: UserFoldersState = []; +const initialUserFoldersState: UserFoldersState = [{title: 'images'}]; export const userFoldersReducer = ( state = initialUserFoldersState, @@ -12,15 +17,17 @@ export const userFoldersReducer = ( ) => { switch (action.type) { case UPDATE_USER_FOLDERS: + if (action.userFolders.includes('images') && action.userFolders.length === 1) return state; return [...state, ...action.userFolders]; + case CLEAR_USER_FOLDERS: + return initialUserFoldersState; default: return state; } }; // UserFolders Selectors -export const selectUserFolders = (state: RootState) => - state.domainData.userFolders; +export const selectUserFolders = (state: RootState) => state.domainData.userFolders; // UserBlogs type UserBlogsState = Array<UserBlog>; @@ -33,6 +40,8 @@ export const userBlogsReducer = ( switch (action.type) { case UPDATE_USER_BLOGS: return [...state, ...action.userBlogs]; + case CLEAR_USER_BLOGS: + return initialUserBlogsState; default: return state; } diff --git a/MaharaMobile/reducers/userTagsReducer.ts b/MaharaMobile/reducers/userTagsReducer.ts index a8bc6ff140c1cccc739b9ec9b7c5c18a7d9da8fe..60d1730d97f5486dd3769659df71b5e8595a712f 100644 --- a/MaharaMobile/reducers/userTagsReducer.ts +++ b/MaharaMobile/reducers/userTagsReducer.ts @@ -1,33 +1,37 @@ import { UserTag } from '../models/models'; -import { RootState } from './reducers'; -import { UPDATE_USER_TAGS, ADD_TAG, REMOVE_TAG } from '../utils/constants'; +import { + UPDATE_USER_TAGS, + ADD_TAG, + REMOVE_TAG, + CLEAR_USER_TAGS, +} from '../utils/constants'; +import { RootState } from './rootReducer'; const initialState: Array<UserTag> = []; // Reducer -const addTag = (state: any, tag: UserTag) => { - return [...state.concat(tag)]; -} +const addTag = (state: any, tag: UserTag) => [...state.concat(tag)]; const removeTag = (state: any, tag: UserTag) => { const newState = { ...state }; delete newState[tag.id]; return newState; -} +}; export const userTagsReducer = (state = initialState, action: any) => { - switch (action.type) { case ADD_TAG: return addTag(state, action.payload); case REMOVE_TAG: return removeTag(state, action.payload); case UPDATE_USER_TAGS: - return [...state.concat(action.userTags)] + return [...state.concat(action.userTags)]; + case CLEAR_USER_TAGS: + return initialState; default: return state; } -} +}; // Selector export const selectUserTags = (state: RootState) => state.domainData.userTags; diff --git a/MaharaMobile/screens/AuthLoadingScreen/AuthLoadingScreen.tsx b/MaharaMobile/screens/AuthLoadingScreen/AuthLoadingScreen.tsx new file mode 100644 index 0000000000000000000000000000000000000000..a15d0416ebf4aeb61929f9d2b1a297e1be830aff --- /dev/null +++ b/MaharaMobile/screens/AuthLoadingScreen/AuthLoadingScreen.tsx @@ -0,0 +1,138 @@ +import React, {Component} from 'react'; +import AsyncStorage from '@react-native-community/async-storage'; +import { View, ActivityIndicator, StatusBar, StyleSheet } from 'react-native'; +import {connect} from 'react-redux'; +import { + updateUserBlogs, + updateUserFolders, + updateUserName, + updateUserTags, + addToken, + updateLoginTypes, + updateUrl, + addFileToUploadList, + addJournalEntryToUploadList, + updateProfilePic +} from '../../actions/actions'; +import {setUpGuest} from '../../utils/authHelperFunctions'; +import {MaharaPendingFile, PendingJournalEntry} from '../../models/models'; + +type Props = { + navigation: any; + dispatch: any; +}; + +type State = {}; + +class AuthLoadingScreen extends Component<Props, State> { + componentDidMount() { + this.bootstrapAsync(); + } + + // Fetch userToken from storage then navigate to our appropriate place + bootstrapAsync = async () => { + const userToken = await AsyncStorage.getItem('userToken'); + // This will switch to the App screen or Auth screen and this loading + // screen will be unmounted and thrown away. + if (userToken !== null) { + this.retrieveAsync().then(this.props.navigation.navigate('App')); + } else this.props.navigation.navigate('Auth'); + }; + + retrieveAsync = async () => { + try { + // Sort data strings + await AsyncStorage.getItem('username').then(async (result: any) => { + await this.props.dispatch(updateUserName(result)); + }); + + let localLogin = false; + let tokenLogin = false; + let ssoLogin = false; + await AsyncStorage.getItem('localLogin').then(async (result: any) => { + if (result) localLogin = JSON.parse(result); + }); + await AsyncStorage.getItem('tokenLogin').then(async (result: any) => { + if (result) tokenLogin = JSON.parse(result); + }); + + await AsyncStorage.getItem('ssoLogin').then(async (result: any) => { + if (result) ssoLogin = JSON.parse(result); + }); + this.props.dispatch( + updateLoginTypes(null, localLogin, tokenLogin, ssoLogin) + ); + + await AsyncStorage.getItem('url').then(async (result: any) => { + await this.props.dispatch(updateUrl(result)); + }); + + await AsyncStorage.getItem('userToken').then(async (result: any) => { + await this.props.dispatch(addToken(result)); + }); + await AsyncStorage.getItem('profileIcon').then(async (result: any) => { + await this.props.dispatch(updateProfilePic(result)); + }); + + // Sort data objects + await AsyncStorage.getItem('userTags').then(async (result: any) => { + if (result) { + this.props.dispatch(updateUserTags(this.parseJSON(result))); + } + }); + + await AsyncStorage.getItem('userFolders').then(async (result: any) => { + if (result) { + await this.props.dispatch(updateUserFolders(this.parseJSON(result))); + } + }); + + await AsyncStorage.getItem('userBlogs').then(async (result: any) => { + if (result) { + await this.props.dispatch(updateUserBlogs(this.parseJSON(result))); + } + }); + + await AsyncStorage.getItem('uploadFiles').then(async (result: any) => { + if (result) { + const uploadFilesList = this.parseJSON(result); + uploadFilesList.forEach((uploadFile: MaharaPendingFile) => + this.props.dispatch(addFileToUploadList(uploadFile)) + ); + } + }); + + await AsyncStorage.getItem('uploadJEntries').then(async (result: any) => { + if (result) { + const uploadJEntries = this.parseJSON(result); + uploadJEntries.forEach((jEntry: PendingJournalEntry) => + this.props.dispatch(addJournalEntryToUploadList(jEntry)) + ); + } + }); + } catch (error) { + console.log(`Error getting items from AsyncStorage: ${error}`); + } + }; + + parseJSON = (jsonString: string) => JSON.parse(jsonString); + + render() { + return ( + <View style={styles.container}> + <ActivityIndicator /> + <StatusBar barStyle="default" /> + </View> + ); + } +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: 'center', + justifyContent: 'center' + } +}); + +export default connect()(AuthLoadingScreen); diff --git a/MaharaMobile/screens/LoginScreen/LoginScreen.tsx b/MaharaMobile/screens/LoginScreen/LoginScreen.tsx index 3868c43f7e45fe49d2428fd63b6c8fd4c060d83a..64991ab5ea172357b31dcf5b6442906ef222918d 100644 --- a/MaharaMobile/screens/LoginScreen/LoginScreen.tsx +++ b/MaharaMobile/screens/LoginScreen/LoginScreen.tsx @@ -1,19 +1,27 @@ -/* eslint-disable import/extensions */ import React, { Component } from 'react'; -import { View } from 'react-native'; +import { View, Alert } from 'react-native'; import { connect } from 'react-redux'; -import { addToken } from '../../actions/actions'; +import AsyncStorage from '@react-native-community/async-storage'; +import { addToken, updateGuestStatus } from '../../actions/actions'; import TokenInput from '../../components/TokenInput/TokenInput'; -import { sendTokenLogin } from '../../utils/helperFunctions'; import generic from '../../assets/styles/generic'; import { selectUrl, selectTokenLogin, selectSsoLogin, selectLocalLogin, - selectLoginState, + selectIsGuestStatus } from '../../reducers/loginInfoReducer'; import { RootState } from '../../reducers/reducers'; +import { + selectUserBlogs, + selectUserFolders +} from '../../reducers/userArtefactsReducer'; +import { UserFolder, UserBlog } from '../../models/models'; +import { + updatePendingItemsOnLogin, + fetchUserOnTokenLogin +} from '../../utils/authHelperFunctions'; type Props = { dispatch: any; @@ -23,7 +31,9 @@ type Props = { ssoLogin: boolean; localLogin: boolean; loginType: boolean; - isLoggedIn: boolean; + userFolders: Array<UserFolder>; + userBlogs: Array<UserBlog>; + isGuest: boolean; }; type State = { @@ -35,12 +45,12 @@ export class LoginScreen extends Component<Props, State> { super(props); this.state = { - token: '', + token: '' }; } login = () => { - const {url} = this.props; + const { url } = this.props; const serverUrl = `${url}webservice/rest/server.php?alt=json`; const body = { @@ -56,42 +66,64 @@ export class LoginScreen extends Component<Props, State> { const requestOptions = { method: 'POST', headers: { - 'Content-Type': 'application/json', + 'Content-Type': 'application/json' }, - body: JSON.stringify(body), + body: JSON.stringify(body) }; - this.props - .dispatch(sendTokenLogin(serverUrl, requestOptions)) - .then(() => this.props.navigation.navigate('Add')); + this.props.dispatch(fetchUserOnTokenLogin(serverUrl, requestOptions)) + .then(() => { + this.props.dispatch(addToken(this.state.token)); + this.signInAsync(); + }) + .then(() => this.props.navigation.navigate('App')); }; - setToken = (input: string) => { - const token = input.trim(); - + updateToken = (input: string) => { + const newToken = input.trim(); this.setState({ - token, + token: newToken }); }; - handleToken = () => { - const {token} = this.state; + verifyToken = () => { this.login(); - this.props.dispatch(addToken(token)); + }; + + /** + * Save user token to async storage + */ + signInAsync = async () => { + if (this.state.token.length < 1) { + Alert.alert('Nothing entered in field'); + } else if (this.props.isGuest) { + await this.props.dispatch(updateGuestStatus(false)); + updatePendingItemsOnLogin( + this.props.dispatch, + this.props.userBlogs, + this.props.userFolders, + this.state.token, + this.props.url + ); + await AsyncStorage.setItem('userToken', this.state.token); + } }; static navigationOptions = { - header: null, + header: null }; render() { - const {params} = this.props.navigation.state; - const {loginType} = params; + const { params } = this.props.navigation.state; + const { loginType } = params; return ( <View style={generic.view}> {loginType === 'token' ? ( - <TokenInput handleToken={this.handleToken} setToken={this.setToken} /> + <TokenInput + onVerifyToken={this.verifyToken} + onUpdateToken={this.updateToken} + /> ) : null} </View> ); @@ -103,7 +135,9 @@ const mapStateToProps = (state: RootState) => ({ tokenLogin: selectTokenLogin(state), ssoLogin: selectSsoLogin(state), localLogin: selectLocalLogin(state), - isLoggedIn: selectLoginState(state), + userBlogs: selectUserBlogs(state), + userFolders: selectUserFolders(state), + isGuest: selectIsGuestStatus(state) }); export default connect(mapStateToProps)(LoginScreen); diff --git a/MaharaMobile/screens/PendingScreen/PendingScreen.tsx b/MaharaMobile/screens/PendingScreen/PendingScreen.tsx index ec4e9663165b7a28c2466594c516dfed7eb1657e..ee6de71ba2c4ed5a89b916978ad272e9071f105c 100644 --- a/MaharaMobile/screens/PendingScreen/PendingScreen.tsx +++ b/MaharaMobile/screens/PendingScreen/PendingScreen.tsx @@ -3,7 +3,6 @@ import { Text, View, TouchableOpacity } from 'react-native'; import { connect } from 'react-redux'; import styles from './PendingScreen.style'; -import { buttons } from '../../assets/styles/buttons'; import { removeUploadFile, removeUploadJEntry } from '../../actions/actions' import { MaharaPendingFile, PendingJournalEntry } from '../../models/models'; import Spinner from '../../components/Spinner/Spinner' @@ -12,27 +11,25 @@ import { uploadItemToMahara } from '../../utils/helperFunctions'; import { RootState } from '../../reducers/reducers'; import { selectAllUploadFiles, selectAllUploadFilesIds } from '../../reducers/uploadFilesReducer'; import { selectAllJEntriesIds, selectAllJEntries } from '../../reducers/uploadJEntriesReducer'; -import Header from '../../components/Header/Header'; - -type Props = - { - uploadFiles: Array<MaharaPendingFile>; - uploadJEntries: Array<PendingJournalEntry>; - uploadFilesIds: Array<string>; - uploadJEntriesIds: Array<string>; - dispatch: any; - navigation: any; - } +import { selectUserName } from '../../reducers/loginInfoReducer'; +import { buttons } from '../../assets/styles/buttons'; -type State = - { - uploadRequestPending: boolean; - uploadRequestReceived: boolean; - successMessage: string; - selectedFiles: Array<any>; - uploadItemsExist: boolean; - } +type Props = { + uploadFiles: Array<MaharaPendingFile>; + uploadJEntries: Array<PendingJournalEntry>; + uploadFilesIds: Array<string>; + uploadJEntriesIds: Array<string>; + dispatch: any; + navigation: any; + userName: string; +} +type State = { + uploadRequestPending: boolean; + uploadRequestReceived: boolean; + successMessage: string; + uploadItemsExist: boolean; +} export class PendingScreen extends Component<Props, State> { constructor(props: Props) { @@ -42,37 +39,48 @@ export class PendingScreen extends Component<Props, State> { uploadRequestPending: false, uploadRequestReceived: false, successMessage: '', - selectedFiles: [], uploadItemsExist: (this.props.uploadFiles.length + this.props.uploadJEntries.length > 0 ? true : false), - } + }; } - static navigationOptions = { - // header: null - }; - pendingDisplay = () => { - const { uploadRequestPending, uploadRequestReceived, successMessage } = this.state; - // there are items to upload + const { uploadRequestPending, uploadRequestReceived, successMessage } = this.state let list: Array<any> = []; if (this.props.uploadFilesIds.length > 0) list = list.concat(this.props.uploadFiles); if (this.props.uploadJEntriesIds.length > 0) list = list.concat(this.props.uploadJEntries) if (this.state.uploadItemsExist) { - return ( - <View> - {this.renderPendingList(list)} - </View> - ) - // no items to upload - } else { - if (uploadRequestPending) return <Spinner /> - else if (!uploadRequestPending && uploadRequestReceived) return <Text>{successMessage}</Text> - else return <Text>No pending uploads</Text> + return <View>{this.renderPendingList(list)}</View>; } + if (uploadRequestPending) return <Spinner />; + if (!uploadRequestPending && uploadRequestReceived) return <Text>{successMessage}</Text>; + return <Text>No pending uploads</Text>; + }; + + /** + * When 'Remove' is pressed, filter out the item with the given id and update the UploadList. + */ + onRemove = (itemId: string) => { + this.props.dispatch(removeUploadFile(itemId)); + this.props.dispatch(removeUploadJEntry(itemId)); } + onUploadClick = () => { + try { + this.props.uploadFiles.forEach(file => this.props.dispatch(uploadItemToMahara(file.url, file.maharaFormData))); + this.props.uploadJEntries.forEach(journalEntry => this.props.dispatch(uploadItemToMahara(journalEntry.url, journalEntry.journalEntry))); + this.props.uploadFiles.forEach(file => this.props.dispatch(removeUploadFile(file.id))) + this.props.uploadJEntries.forEach(journalEntry => this.props.dispatch(removeUploadJEntry(journalEntry.id))) + } catch (error) { + console.log('onUploadClick error', error); + } + }; + + static navigationOptions = { + headerTitle: 'Pending items' + }; + /** * Renders a PendingList * @param dataList array of files and journal entries @@ -85,53 +93,42 @@ export class PendingScreen extends Component<Props, State> { onEdit={this.onEdit} navigation={this.props.navigation} /> - ) - } - - onUploadClick = () => { - this.props.uploadFiles.forEach(file => this.props.dispatch(uploadItemToMahara(file.url, file.maharaFormData))); - this.props.uploadJEntries.forEach(journalEntry => this.props.dispatch(uploadItemToMahara(journalEntry.url, journalEntry.journalEntry))); - this.props.uploadFiles.forEach(file => this.props.dispatch(removeUploadFile(file.id))); - this.props.uploadJEntries.forEach(journalEntry => this.props.dispatch(removeUploadJEntry(journalEntry.id))); - } - - /** - * When 'Remove' is pressed, filter out the item with the given id and update the UploadList. - */ - onRemove = (itemId: string) => { - this.props.dispatch(removeUploadFile(itemId)); - this.props.dispatch(removeUploadJEntry(itemId)); + ); } onEdit = (item: MaharaPendingFile | PendingJournalEntry) => { - this.props.navigation.navigate({routeName: 'AddFile', params: { item: item }}); + this.props.navigation.navigate({ routeName: 'AddFile', params: { item: item } }); } render() { return ( - <View style={styles.app} > - <Header navigation={this.props.navigation} /> - <Text>Pending Uploads</Text> - <View style={styles.listContainer}> - {this.pendingDisplay()} - </View> + <View style={styles.app}> + <View style={styles.listContainer}>{this.pendingDisplay()}</View> <View style={styles.buttonContainer}> - <TouchableOpacity onPress={this.onUploadClick}> - <Text style={buttons.lg}>Upload to your Mahara</Text> - </TouchableOpacity> + {this.props.userName !== 'guest' ? ( + <TouchableOpacity onPress={this.onUploadClick}> + <Text style={buttons.lg}>Upload to your Mahara</Text> + </TouchableOpacity> + ) : ( + <TouchableOpacity + onPress={() => this.props.navigation.navigate('Auth')}> + <Text style={buttons.lg}>Please login</Text> + </TouchableOpacity>) + } </View> </View> ); } -}; +} const mapStateToProps = (state: RootState) => { return { uploadFiles: selectAllUploadFiles(state), uploadFilesIds: selectAllUploadFilesIds(state), uploadJEntries: selectAllJEntries(state), - uploadJEntriesIds: selectAllJEntriesIds(state) + uploadJEntriesIds: selectAllJEntriesIds(state), + userName: selectUserName(state) }; -} +}; export default connect(mapStateToProps)(PendingScreen); diff --git a/MaharaMobile/screens/ProfileScreen/ProfileScreen.tsx b/MaharaMobile/screens/ProfileScreen/ProfileScreen.tsx index 70a7eb7250ef42a8384eb342699ae40162c8c3e8..bdb3692729fa0a4e10619a96d9fae4ec24930b7c 100644 --- a/MaharaMobile/screens/ProfileScreen/ProfileScreen.tsx +++ b/MaharaMobile/screens/ProfileScreen/ProfileScreen.tsx @@ -1,8 +1,7 @@ import React, { Component } from 'react'; -import { View, TouchableOpacity, Text } from 'react-native'; +import { View } from 'react-native'; import { connect } from 'react-redux'; -import RNFetchBlob from 'rn-fetch-blob'; - +import AsyncStorage from '@react-native-community/async-storage'; import Header from '../../components/Header/Header'; import Profile from '../../components/Profile/Profile'; import styles from './ProfileScreen.style'; @@ -10,15 +9,23 @@ import { selectUrl, selectToken, selectUserName, + selectProfileIcon } from '../../reducers/loginInfoReducer'; -import { RootState } from '../../reducers/reducers'; -import { buttons } from '../../assets/styles/buttons'; +import MediumButton from '../../components/MediumButton/MediumButton'; +import { clearReduxData, fetchProfilePic } from '../../utils/authHelperFunctions'; +import { selectAllJEntriesIds } from '../../reducers/uploadJEntriesReducer'; +import { selectAllUploadFilesIds } from '../../reducers/uploadFilesReducer'; +import { RootState } from '../../reducers/rootReducer'; type Props = { navigation: any; // need to double check type for this token: string; userName: string; url: string; + profileIcon: string; + jEntriesIds: string[]; + fileIds: string[]; + dispatch: any; }; type State = { @@ -29,62 +36,73 @@ export class ProfileScreen extends Component<Props, State> { constructor(props: Props) { super(props); this.state = { - profileIcon: '', + profileIcon: '' }; } componentDidMount() { - this.receiveProfilePic(); + this.getProfilePic(); } - receiveProfilePic = async () => { - const api = 'module_mobileapi_get_user_profileicon&height=100&width=100'; - const wstoken = this.props.token; - const serverUrl = - 'https://master.dev.mahara.org/module/mobileapi/download.php?wsfunction=' + - api + - '&wstoken=' + - wstoken; + getProfilePic = async () => { + if (!this.props.token || this.props.token === 'guest') return; + fetchProfilePic(this.props.dispatch, this.props.token); + }; - RNFetchBlob.config({ - fileCache: true, - }) - .fetch('GET', serverUrl) - .then(res => { - const image = `file://${res.path()}`; - this.setState({ - profileIcon: image, - }); - }) - .catch(error => { - // error handling - console.log(error); - }); + signOutAsync = async () => { + await AsyncStorage.clear(); + clearReduxData(this.props.dispatch); + this.props.navigation.navigate('Auth'); + }; + + generateProfileScreen = () => { + if (this.props.token !== 'guest') { + return ( + <View> + <View style={styles.container}> + <Profile + name={this.props.userName} + profileIcon={this.props.profileIcon} + /> + </View> + <View style={{ marginTop: 450 }}> + <MediumButton title="Logout" onPress={this.signOutAsync} /> + </View> + </View> + ); + } + + return ( + <View> + <View style={styles.container}> + <Profile + name={this.props.userName} + profileIcon={this.state.profileIcon || this.props.profileIcon} + /> + </View> + <View style={styles.buttons}> + <MediumButton + title="Logout as Guest" + onPress={() => this.signOutAsync()} + /> + <MediumButton + title="Login" + onPress={() => this.props.navigation.navigate('Auth')} + /> + </View> + </View> + ); }; static navigationOptions = { - header: null, + header: null }; render() { return ( <View style={styles.app}> <Header navigation={this.props.navigation} /> - <View style={styles.container}> - {this.props.userName !== '' ? ( - <Profile - name={this.props.userName} - profileIcon={this.state.profileIcon} - /> - ) : ( - <View style={styles.buttons}> - <TouchableOpacity - onPress={() => this.props.navigation.navigate('Auth')}> - <Text style={[buttons.md, styles.buttons]}>Login</Text> - </TouchableOpacity> - </View> - )} - </View> + <View style={styles.container}>{this.generateProfileScreen()}</View> </View> ); } @@ -94,6 +112,9 @@ const mapStateToProps = (state: RootState) => ({ url: selectUrl(state), token: selectToken(state), userName: selectUserName(state), + profileIcon: selectProfileIcon(state), + jEntryIds: selectAllJEntriesIds(state), + fileIds: selectAllUploadFilesIds(state) }); export default connect(mapStateToProps)(ProfileScreen); diff --git a/MaharaMobile/screens/SiteCheckScreen/SiteCheckScreen.tsx b/MaharaMobile/screens/SiteCheckScreen/SiteCheckScreen.tsx index e9e1fbbea681776a44b873e9b66c4101711c42d5..539f9973444a12d773231eda049e6d538f4bafc8 100644 --- a/MaharaMobile/screens/SiteCheckScreen/SiteCheckScreen.tsx +++ b/MaharaMobile/screens/SiteCheckScreen/SiteCheckScreen.tsx @@ -4,8 +4,14 @@ import { connect } from 'react-redux'; import { checkLoginTypes } from '../../actions/actions'; import LoginType from '../../components/LoginType/LoginType'; import generic from '../../assets/styles/generic'; -import { RootState } from '../../reducers/reducers'; -import { selectUrl, selectTokenLogin, selectSsoLogin, selectLocalLogin } from '../../reducers/loginInfoReducer'; +import { + selectUrl, + selectTokenLogin, + selectSsoLogin, + selectLocalLogin +} from '../../reducers/loginInfoReducer'; +import { setUpGuest } from '../../utils/authHelperFunctions'; +import { RootState } from '../../reducers/rootReducer'; type Props = { dispatch: any; @@ -31,7 +37,7 @@ const initialState: State = { loginType: '', serverPing: false, isInputHidden: false, - enterUrlWarning: false, + enterUrlWarning: false }; export class SiteCheckScreen extends Component<Props, State> { @@ -43,7 +49,7 @@ export class SiteCheckScreen extends Component<Props, State> { setLoginType = (loginType: string) => { this.props.navigation.navigate('Login', { - loginType, + loginType }); }; @@ -53,12 +59,12 @@ export class SiteCheckScreen extends Component<Props, State> { if (serverUrl.length === 0) { this.setState({ enterUrlWarning: true, - url: '', + url: '' }); return; } this.setState({ - enterUrlWarning: false, + enterUrlWarning: false }); if (serverUrl.slice(-1) !== '/') { @@ -79,34 +85,30 @@ export class SiteCheckScreen extends Component<Props, State> { try { await this.props.dispatch(checkLoginTypes(serverUrl)); - if ( - this.props.tokenLogin || this.props.localLogin || this.props.ssoLogin ) { + if (this.props.tokenLogin || this.props.localLogin || this.props.ssoLogin) { this.setState({ serverPing: true, isInputHidden: true, - errorMessage: '', + errorMessage: '' }); } } catch (error) { this.setState({ errorMessage: error.message }); - console.log(error); + console.error(error); } }; - resetForm = () => { - this.setState(initialState); - }; - - skip = () => { - this.props.navigation.navigate('Add'); + skipLogin = () => { + setUpGuest(this.props.dispatch); + this.props.navigation.navigate('App'); }; - static navigationOptions = { - header: null, + resetForm = () => { + this.setState(initialState); }; static navigationOptions = { - header: null, + header: null }; render() { @@ -125,7 +127,8 @@ export class SiteCheckScreen extends Component<Props, State> { ssoLogin={this.props.ssoLogin} tokenLogin={this.props.tokenLogin} errorMessage={this.state.errorMessage} - skip={this.skip} + navigation={this.props.navigation} + onSkip={this.skipLogin} /> </View> ); @@ -136,7 +139,7 @@ const mapStateToProps = (state: RootState) => ({ url: selectUrl(state), tokenLogin: selectTokenLogin(state), ssoLogin: selectSsoLogin(state), - localLogin: selectLocalLogin(state), + localLogin: selectLocalLogin(state) }); export default connect(mapStateToProps)(SiteCheckScreen); diff --git a/MaharaMobile/utils/authHelperFunctions.ts b/MaharaMobile/utils/authHelperFunctions.ts new file mode 100644 index 0000000000000000000000000000000000000000..cbe4d82c06f80d26aa3a31da4d35aa6f0fcc3120 --- /dev/null +++ b/MaharaMobile/utils/authHelperFunctions.ts @@ -0,0 +1,141 @@ +import AsyncStorage from '@react-native-community/async-storage'; +import RNFetchBlob from 'rn-fetch-blob'; +import { + UserBlog, + UserFolder, + MaharaPendingFile, + PendingJournalEntry +} from '../models/models'; +import { + clearLoginInfo, + clearUploadFiles, + clearUploadJEntires, + clearUserFolders, + clearUserTags, + clearUserBlogs, + addToken, + updateUserName, + updateUserFolders, + updateUserBlogs, + updateJEntriesOnLogin, + updateUploadFilesOnLogin, + updateUserTags, + updateGuestStatus, + addJournalEntryToUploadList, + addFileToUploadList, + updateProfilePic +} from '../actions/actions'; + +export function fetchUserOnTokenLogin(serverUrl: string, requestOptions: any) { + return async function(dispatch: any) { + try { + const response = await fetch(serverUrl, requestOptions); + const json = await response.json(); + dispatch(updateUserName(json.userprofile.myname)); + dispatch(updateUserTags(json.tags.tags)); + dispatch(updateUserBlogs(json.blogs.blogs)); + dispatch(updateUserFolders(json.folders.folders)); + } catch (error) { + // errorHandle(error); + } + }; +} + +export const clearReduxData = async (dispatch: any) => { + try { + dispatch(clearLoginInfo()); + dispatch(clearUploadFiles()); + dispatch(clearUploadJEntires()); + dispatch(clearUserBlogs()); + dispatch(clearUserFolders()); + dispatch(clearUserTags()); + } catch (error) { + console.log('clearReduxData$', error); + } +}; + +export const setUpGuest = async (dispatch: any) => { + await dispatch(addToken('guest')); + await dispatch(updateUserName('guest')); + await dispatch(updateUserFolders([{id: -1, title: 'images'}])); + await dispatch( + updateUserBlogs([ + { + id: -1, + title: 'Guest Blog', + description: null, + locked: false, + numblogposts: -1 + } + ]) + ); + await AsyncStorage.getItem('uploadFiles').then(async (result: any) => { + if (result) { + const uploadFilesList = parseJSON(result); + uploadFilesList.forEach((uploadFile: MaharaPendingFile) => + dispatch(addFileToUploadList(uploadFile)) + ); + } + }); + + await AsyncStorage.getItem('uploadJEntries').then(async (result: any) => { + if (result) { + const uploadJEntries = parseJSON(result); + uploadJEntries.forEach((jEntry: PendingJournalEntry) => + dispatch(addJournalEntryToUploadList(jEntry)) + ); + } + }); + await dispatch(updateGuestStatus(true)); +}; + +const parseJSON = (jsonString: string) => JSON.parse(jsonString); + +export const arrayToObject = (array: Array<any>) => { + const arrayCopy = [...array]; + return arrayCopy.reduce((obj, item) => { + const objCopy = {...obj}; + objCopy[item.id] = item; + return objCopy; + }, {}); +}; + +/** + * Updates default guest data to user info once logged in. + * - Tokens, blogs, folders as well as their respective ids need to be updated + * - Existing pending uploadFiles and uploadJEntries once users login + * - Rerieved Mahara data to replace guest data be able to upload items. + */ +export const updatePendingItemsOnLogin = async ( + dispatch: any, + userBlogs: Array<UserBlog>, + userFolders: Array<UserFolder>, + token: string, + urlDomain: string +) => { + await dispatch(updateJEntriesOnLogin(token, urlDomain, userBlogs)); + await dispatch(updateUploadFilesOnLogin(token, urlDomain, userFolders)); +}; + +export const fetchProfilePic = async (dispatch: any, token: string) => { + const api = 'module_mobileapi_get_user_profileicon&height=100&width=100'; + const wstoken = token; + const serverUrl = `https://master.dev.mahara.org/module/mobileapi/download.php?wsfunction=${api}&wstoken=${wstoken}`; + + let profilePic = ''; + + RNFetchBlob.config({ + fileCache: true + }) + .fetch('GET', serverUrl) + .then((res) => { + profilePic = `file://${res.path()}`; + dispatch(updateProfilePic(profilePic)); + }) + .catch((error) => { + // error handling + console.log(error); + }); + + return profilePic; +}; diff --git a/MaharaMobile/utils/constants.ts b/MaharaMobile/utils/constants.ts index 62fb4cf9c4c01200041845604ff41f624d713358..e73a9a23a348cb0aeea1ac78abed5d24c12dcd9e 100644 --- a/MaharaMobile/utils/constants.ts +++ b/MaharaMobile/utils/constants.ts @@ -1,18 +1,34 @@ // action types - payloads of information that send data from your application to your store // DOMAIN DATA +// userTagsReducer export const ADD_TAG = 'ADD_TAG'; -export const ADD_USER = 'ADD_USER'; +export const REMOVE_TAG = 'REMOVE_TAG'; +export const UPDATE_USER_TAGS = 'UPDATE_USER_TAGS'; +export const CLEAR_USER_TAGS = 'CLEAR_USER_TAGS'; +// loginInfoReducer export const ADD_TOKEN = 'ADD_TOKEN'; export const UPDATE_USERNAME = 'UPDATE_USERNAME'; +export const UPDATE_SERVER_URL = 'UPDATE_SERVER_URL'; +export const CLEAR_LOGIN_INFO = 'CLEAR_LOGIN_INFO'; +export const UPDATE_LOGIN_TYPES = 'UPDATE_LOGIN_TYPES'; +export const UPDATE_URL = 'UPDATE_URL'; +export const UPDATE_PROFILE_ICON = 'UPDATE_PROFILE_ICON'; +export const UPDATE_GUEST_STATUS = 'UPDATE_GUEST_STATUS'; +// userArtefactsReducer export const UPDATE_USER_BLOGS = 'UPDATE_USER_BLOGS'; export const UPDATE_USER_FOLDERS = 'UPDATE_USER_FOLDERS'; -export const UPDATE_USER_TAGS = 'UPDATE_USER_TAGS'; -export const UPDATE_SERVER_URL = 'UPDATE_SERVER_URL'; -export const REMOVE_TAG = 'REMOVE_TAG'; +export const CLEAR_USER_BLOGS = 'CLEAR_USER_BLOGS'; +export const CLEAR_USER_FOLDERS = 'CLEAR_USER_FOLDERS'; // APP STATE +// uploadFilesReducer export const ADD_UPLOAD_FILE = 'ADD_FILE_TO_UPLOAD_LIST'; -export const ADD_UPLOAD_JOURNAL_ENTRY = 'ADD_JOURNAL_ENTRY_TO_UPLOAD_LIST'; export const REMOVE_UPLOAD_FILE = 'REMOVE_UPLOAD_FILE'; +export const CLEAR_UPLOAD_FILES = 'CLEAR_UPLOAD_FILES'; +export const UPDATE_UPLOAD_FILES_ON_LOGIN = 'UPDATE_UPLOAD_FILES_ON_LOGIN'; +// uploadJEntriesReducer +export const ADD_UPLOAD_JOURNAL_ENTRY = 'ADD_JOURNAL_ENTRY_TO_UPLOAD_LIST'; export const REMOVE_UPLOAD_JOURNAL_ENTRY = 'REMOVE_UPLOAD_JOURNAL_ENTRY'; +export const CLEAR_UPLOAD_J_ENTRIES = 'CLEAR_UPLOAD_J_ENTRIES'; +export const UPDATE_J_ENTRIES_ON_LOGIN = 'UPDATE_J_ENTRIES_ON_LOGIN';