Fakultas Ilmu Komputer UI

Commit add3980a authored by Wulan Mantiri's avatar Wulan Mantiri
Browse files

Merge branch 'PBI-11-profile_client_layout' into 'staging'

Implement client profile UI layout, download and view PDF functionality

See merge request !45
parents d59a802f d770b087
Pipeline #76228 passed with stages
in 67 minutes and 17 seconds
......@@ -3,6 +3,8 @@
package="com.dietela_mobile">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:name=".MainApplication"
......
......@@ -4,6 +4,7 @@ import android.app.Application;
import android.content.Context;
import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
import com.rnfs.RNFSPackage;
import com.reactnativecommunity.asyncstorage.AsyncStoragePackage;
import com.oblador.vectoricons.VectorIconsPackage;
import com.facebook.react.ReactInstanceManager;
......
rootProject.name = 'dietela_mobile'
include ':react-native-fs'
project(':react-native-fs').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fs/android')
include ':react-native-vector-icons'
project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android')
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
......
......@@ -16,6 +16,8 @@ target 'dietela_mobile' do
pod 'RNGoogleSignin', :path => "../node_modules/@react-native-google-signin/google-signin"
pod 'RNFS', :path => '../node_modules/react-native-fs'
target 'dietela_mobileTests' do
inherit! :complete
# Pods for testing
......
jest.mock('react-native-fs', () => {
return {
mkdir: jest.fn(),
moveFile: jest.fn(),
copyFile: jest.fn(),
pathForBundle: jest.fn(),
pathForGroup: jest.fn(),
getFSInfo: jest.fn(),
getAllExternalFilesDirs: jest.fn(),
unlink: jest.fn(),
exists: jest.fn(),
stopDownload: jest.fn(),
resumeDownload: jest.fn(),
isResumable: jest.fn(),
stopUpload: jest.fn(),
completeHandlerIOS: jest.fn(),
readDir: jest.fn(),
readDirAssets: jest.fn(),
existsAssets: jest.fn(),
readdir: jest.fn(),
setReadable: jest.fn(),
stat: jest.fn(),
readFile: jest.fn(),
read: jest.fn(),
readFileAssets: jest.fn(),
hash: jest.fn(),
copyFileAssets: jest.fn(),
copyFileAssetsIOS: jest.fn(),
copyAssetsVideoIOS: jest.fn(),
writeFile: jest.fn(),
appendFile: jest.fn(),
write: jest.fn(),
downloadFile: jest.fn(),
uploadFiles: jest.fn(),
touch: jest.fn(),
MainBundlePath: jest.fn(),
CachesDirectoryPath: jest.fn(),
DocumentDirectoryPath: jest.fn(),
ExternalDirectoryPath: jest.fn(),
ExternalStorageDirectoryPath: jest.fn(),
TemporaryDirectoryPath: jest.fn(),
LibraryDirectoryPath: jest.fn(),
PicturesDirectoryPath: jest.fn(),
};
});
export {};
import * as ROUTES from 'constants/routes';
import {
// Extended Questionnaire
ExtendedQuestionnaire,
ConsentForm,
Questionnaire1,
Questionnaire2,
Questionnaire3,
Questionnaire4,
Questionnaire5,
ClientProfile,
// Others
AllAccessQuestionnaire,
Checkout,
ChoosePlan,
ComingSoonPage,
DietelaQuizResult,
InitialPage,
ManualRegistrationPage,
......@@ -11,19 +21,11 @@ import {
ProgramDetail,
NutritionistDetail,
PaymentResult,
ExtendedQuestionnaire,
ClientListNutritionist,
ReadOnlyNutritionistRecommendation,
NutritionistAdminLogin,
} from 'scenes';
import { FC } from 'react';
import {
ConsentForm,
Questionnaire1,
Questionnaire2,
Questionnaire3,
Questionnaire4,
Questionnaire5,
} from 'scenes/questionnaire/ExtendedQuestionnaire/components';
export interface NavRoute {
name: string;
......@@ -159,12 +161,20 @@ export const onboardingClientNavigation: NavRoute[] = [
export const clientNavigation: NavRoute[] = [
{
name: ROUTES.clientProfile,
component: ComingSoonPage,
header: 'Profile',
component: ClientProfile,
header: 'Profil Saya',
},
...onboardingClientNavigation,
];
export const adminNavigation: NavRoute[] = [
{
name: ROUTES.clientProfile,
component: ReadOnlyNutritionistRecommendation,
header: 'Profil Klien',
},
];
export const testNavigation: NavRoute[] = [
...navigation,
...onboardingClientNavigation,
......
export { default as useApi } from './useApi';
export { default as useAuthEffect } from './useAuthEffect';
export { default as useDownloadFiles } from './useDownloadFiles';
export { default as useForm } from './useForm';
export { default as useLinkingEffect } from './useLinkingEffect';
import { downloadFile, DocumentDirectoryPath } from 'react-native-fs';
import { Toast } from 'components/core';
import { PermissionsAndroid } from 'react-native';
const useDownloadFiles = (url: string) => {
const fileName = url.split('/').pop();
const extension = fileName?.split('.').pop()?.toUpperCase() || '-';
const askWritePermission = async () =>
await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,
);
const download = async (): Promise<any> => {
const granted = await askWritePermission();
if (!granted) {
return;
}
const path = `${DocumentDirectoryPath}/${fileName}`;
const response = await downloadFile({
fromUrl: url,
toFile: path,
});
return response.promise
.then((res) => {
if (res.statusCode === 200 && res.bytesWritten > 0) {
Toast.show({
type: 'success',
text1: `${extension} berhasil diunduh.`,
text2: `Silakan lihat file ${fileName} dalam penyimpanan HP Anda.`,
});
return {
success: true,
data: `file://${path}`,
};
} else {
Toast.show({
type: 'error',
text1: `${extension} gagal diunduh.`,
text2: 'Terjadi kesalahan pada sisi kami.',
});
return {
success: false,
error: res,
};
}
})
.catch(console.log);
};
return {
fileName,
extension,
download,
pdfViewUrl: `http://docs.google.com/gview?embedded=true&url=${url}`,
};
};
export default useDownloadFiles;
......@@ -8,6 +8,8 @@ export { default as ComingSoonPage } from './common/ComingSoonPage';
export { default as AllAccessQuestionnaire } from './questionnaire/AllAccessQuestionnaire';
export { default as DietelaQuizResult } from './questionnaire/DietelaQuizResult';
export { default as ExtendedQuestionnaire } from './questionnaire/ExtendedQuestionnaire';
export * from './questionnaire/ExtendedQuestionnaire/components';
export * from './questionnaire/NutritionistRecommendation';
export { default as Checkout } from './cart/Checkout';
export { default as ChoosePlan } from './cart/ChoosePlan';
......@@ -17,3 +19,5 @@ export { default as NutritionistDetail } from './cart/NutritionistDetail';
export { default as PaymentResult } from './payment/PaymentResult';
export { default as ClientListNutritionist } from './nutritionist/ClientListNutritionist';
export { default as ClientProfile } from './profile/ClientProfile';
import React from 'react';
import { render, fireEvent } from '@testing-library/react-native';
import ClientProfile from '.';
const mockedNavigate = jest.fn();
jest.mock('@react-navigation/native', () => {
return {
useNavigation: () => ({
navigate: mockedNavigate,
}),
};
});
describe('ClientProfile', () => {
it('renders correctly', () => {
const { getByText } = render(<ClientProfile />);
const changeProfileButton = getByText(/ubah profil/i);
expect(changeProfileButton).toBeTruthy();
fireEvent.press(changeProfileButton);
expect(mockedNavigate).toBeCalled();
});
});
import React, { FC } from 'react';
import { useNavigation } from '@react-navigation/native';
import { Button } from 'react-native-elements';
import * as ROUTES from 'constants/routes';
import { ReadOnlyNutritionistRecommendation } from 'scenes/questionnaire/NutritionistRecommendation';
import { Section } from 'components/layout';
import { typographyStyles } from 'styles';
import { styles } from './styles';
const ClientProfile: FC = () => {
const navigation = useNavigation();
return (
<ReadOnlyNutritionistRecommendation>
<Section>
<Button
title="ubah profil"
type="outline"
onPress={() => navigation.navigate(ROUTES.extendedQuestionnaire)}
buttonStyle={styles.buttonStyle}
titleStyle={[typographyStyles.overlineBig, styles.titleStyle]}
/>
</Section>
</ReadOnlyNutritionistRecommendation>
);
};
export default ClientProfile;
import { StyleSheet } from 'react-native';
import { colors } from 'styles';
export const styles = StyleSheet.create({
buttonStyle: {
borderColor: colors.buttonYellow,
borderWidth: 1,
},
titleStyle: {
color: colors.textBlack,
},
});
import React from 'react';
import { render } from '@testing-library/react-native';
import ReadOnlyNutritionistRecommendation from '.';
describe('ReadOnlyNutritionistRecommendation', () => {
it('renders correctly', () => {
render(<ReadOnlyNutritionistRecommendation />);
});
});
import React, { FC } from 'react';
import { ScrollView, View } from 'react-native';
import { Text, Button } from 'react-native-elements';
import { WebView } from 'react-native-webview';
import { InfoCard, Loader } from 'components/core';
import { layoutStyles } from 'styles';
import { useDownloadFiles } from 'hooks';
import { styles } from './styles';
const ReadOnlyNutritionistRecommendation: FC = ({ children }) => {
const url = 'http://www.africau.edu/images/default/sample.pdf';
const { download, pdfViewUrl, fileName } = useDownloadFiles(url);
return (
<ScrollView contentContainerStyle={layoutStyles}>
<Text style={styles.header}>Saran Gizi</Text>
<InfoCard content="Anda butuh sayurand buahde fhhehfkwhefkuhbweijkfcbewujkfdc wjdbf" />
<Text style={[styles.header, styles.spacing]}>Saran Gaya Hidup</Text>
<InfoCard content="Anda butuh istirahat" />
<View style={styles.spacing}>
<Text style={styles.header}>Rencana Menu dan Porsi Makan</Text>
</View>
<WebView
source={{ uri: pdfViewUrl }}
style={styles.pdfView}
originWhitelist={['*']}
renderLoading={Loader}
/>
<Button
title={`Unduh ${fileName}`}
type="outline"
icon={{
name: 'file-download',
}}
iconRight
buttonStyle={styles.buttonStyle}
titleStyle={styles.titleStyle}
onPress={download}
/>
{children}
</ScrollView>
);
};
export default ReadOnlyNutritionistRecommendation;
import { StyleSheet, Dimensions } from 'react-native';
import { typography, colors } from 'styles';
export const styles = StyleSheet.create({
header: {
...typography.headingMedium,
fontSize: 18,
marginBottom: 4,
},
spacing: {
marginTop: 20,
},
pdfView: {
height: Dimensions.get('window').height * 0.6,
flex: 1,
marginBottom: 10,
},
buttonStyle: {
backgroundColor: colors.primaryYellow,
},
titleStyle: {
color: 'black',
paddingRight: 6,
},
});
export { default as ReadOnlyNutritionistRecommendation } from './ReadOnly';
......@@ -1913,6 +1913,11 @@ balanced-match@^1.0.0:
resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz"
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
base-64@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/base-64/-/base-64-0.1.0.tgz#780a99c84e7d600260361511c4877613bf24f6bb"
integrity sha1-eAqZyE59YAJgNhURxId2E78k9rs=
base64-js@^1.1.2, base64-js@^1.2.3:
version "1.5.1"
resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz"
......@@ -2518,16 +2523,11 @@ data-urls@^1.1.0:
whatwg-mimetype "^2.2.0"
whatwg-url "^7.0.0"
dayjs@^1.10.4:
dayjs@^1.10.4, dayjs@^1.8.15:
version "1.10.4"
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.4.tgz#8e544a9b8683f61783f570980a8a80eaf54ab1e2"
integrity sha512-RI/Hh4kqRc1UKLOAf/T5zdMMX5DQIlDxwUe3wSyMMnEbGunnpENCdbUgM+dW7kXidZqCttBrmw7BhN4TMddkCw==
dayjs@^1.8.15:
version "1.10.4"
resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.10.4.tgz"
integrity sha512-RI/Hh4kqRc1UKLOAf/T5zdMMX5DQIlDxwUe3wSyMMnEbGunnpENCdbUgM+dW7kXidZqCttBrmw7BhN4TMddkCw==
debug@2.6.9, debug@^2.2.0, debug@^2.3.3:
version "2.6.9"
resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz"
......@@ -2798,16 +2798,16 @@ escape-html@~1.0.3:
resolved "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz"
integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
escape-string-regexp@2.0.0, escape-string-regexp@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz"
integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==
escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
escape-string-regexp@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz"
integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==
escape-string-regexp@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz"
......@@ -3827,7 +3827,7 @@ internal-slot@^1.0.3:
has "^1.0.3"
side-channel "^1.0.4"
invariant@^2.2.4:
invariant@2.2.4, invariant@^2.2.4:
version "2.2.4"
resolved "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz"
integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==
......@@ -6115,6 +6115,14 @@ react-native-elements@^3.3.2:
react-native-ratings "^7.3.0"
react-native-size-matters "^0.3.1"
react-native-fs@^2.18.0:
version "2.18.0"
resolved "https://registry.yarnpkg.com/react-native-fs/-/react-native-fs-2.18.0.tgz#987b99cc90518ef26663a8d60e62104694b41c21"
integrity sha512-9iQhkUNnN2JNED0in06JwZy88YEVyIGKWz4KLlQYxa5Y2U0U2AZh9FUHtA04oWj+xt2LlHh0LFPCzhmNsAsUDg==
dependencies:
base-64 "^0.1.0"
utf8 "^3.0.0"
react-native-fullwidth-image@^0.1.3:
version "0.1.3"
resolved "https://registry.npmjs.org/react-native-fullwidth-image/-/react-native-fullwidth-image-0.1.3.tgz"
......@@ -6198,6 +6206,14 @@ react-native-vector-icons@^8.1.0:
prop-types "^15.7.2"
yargs "^16.1.1"
react-native-webview@^11.4.4:
version "11.4.4"
resolved "https://registry.yarnpkg.com/react-native-webview/-/react-native-webview-11.4.4.tgz#86e9e285cfb8dc80705af6830448e25b0ef2050c"
integrity sha512-miB6ZVpIPSxFVN5DIm0O59SGhFmeimB6SASR6deIIGXMkrXoHQCrUpnZ3L6AyofK804unNgdiqkqSa1UdH1iSA==
dependencies:
escape-string-regexp "2.0.0"
invariant "2.2.4"
react-native@0.63.4:
version "0.63.4"
resolved "https://registry.npmjs.org/react-native/-/react-native-0.63.4.tgz"
......@@ -7471,6 +7487,11 @@ use@^3.1.0:
resolved "https://registry.npmjs.org/use/-/use-3.1.1.tgz"
integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==
utf8@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/utf8/-/utf8-3.0.0.tgz#f052eed1364d696e769ef058b183df88c87f69d1"
integrity sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==
util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment