Compare commits

...

8 commits

25 changed files with 202 additions and 335 deletions

View file

@ -80,8 +80,8 @@ project.ext.react = [
enableHermes: true, enableHermes: true,
] ]
apply from: '../../node_modules/react-native-unimodules/gradle.groovy'
apply from: "../../node_modules/react-native/react.gradle" apply from: "../../node_modules/react-native/react.gradle"
apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"
/** /**
* Set this to true to create two separate APKs instead of one: * Set this to true to create two separate APKs instead of one:
@ -134,6 +134,7 @@ android {
targetSdkVersion rootProject.ext.targetSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 16 versionCode 16
versionName "2.0.0" versionName "2.0.0"
missingDimensionStrategy 'react-native-camera', 'general'
} }
splits { splits {
abi { abi {
@ -190,7 +191,6 @@ android {
dependencies { dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"]) implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "com.facebook.react:react-native:+" // From node_modules implementation "com.facebook.react:react-native:+" // From node_modules
addUnimodulesDependencies()
if (enableHermes) { if (enableHermes) {
def hermesPath = "../../node_modules/hermes-engine/android/"; def hermesPath = "../../node_modules/hermes-engine/android/";

View file

@ -9,16 +9,6 @@ import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage; import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage; import com.facebook.react.shell.MainReactPackage;
import com.facebook.soloader.SoLoader; import com.facebook.soloader.SoLoader;
import fr.amicaleinsat.application.generated.BasePackageList;
import org.unimodules.adapters.react.ReactAdapterPackage;
import org.unimodules.adapters.react.ModuleRegistryAdapter;
import org.unimodules.adapters.react.ReactModuleRegistryProvider;
import org.unimodules.core.interfaces.Package;
import org.unimodules.core.interfaces.SingletonModule;
import expo.modules.constants.ConstantsPackage;
import expo.modules.permissions.PermissionsPackage;
import expo.modules.filesystem.FileSystemPackage;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.util.Arrays; import java.util.Arrays;
@ -26,10 +16,6 @@ import java.util.List;
import javax.annotation.Nullable; import javax.annotation.Nullable;
public class MainApplication extends Application implements ReactApplication { public class MainApplication extends Application implements ReactApplication {
private final ReactModuleRegistryProvider mModuleRegistryProvider = new ReactModuleRegistryProvider(
new BasePackageList().getPackageList(),
null
);
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override @Override
@ -40,7 +26,6 @@ public class MainApplication extends Application implements ReactApplication {
@Override @Override
protected List<ReactPackage> getPackages() { protected List<ReactPackage> getPackages() {
List<ReactPackage> packages = new PackageList(this).getPackages(); List<ReactPackage> packages = new PackageList(this).getPackages();
packages.add(new ModuleRegistryAdapter(mModuleRegistryProvider));
return packages; return packages;
} }

View file

@ -1,25 +0,0 @@
package fr.amicaleinsat.application.generated;
import java.util.Arrays;
import java.util.List;
import org.unimodules.core.interfaces.Package;
public class BasePackageList {
public List<Package> getPackageList() {
return Arrays.<Package>asList(
new expo.modules.barcodescanner.BarCodeScannerPackage(),
new expo.modules.camera.CameraPackage(),
new expo.modules.constants.ConstantsPackage(),
new expo.modules.errorrecovery.ErrorRecoveryPackage(),
new expo.modules.filesystem.FileSystemPackage(),
new expo.modules.font.FontLoaderPackage(),
new expo.modules.imageloader.ImageLoaderPackage(),
new expo.modules.keepawake.KeepAwakePackage(),
new expo.modules.lineargradient.LinearGradientPackage(),
new expo.modules.location.LocationPackage(),
new expo.modules.permissions.PermissionsPackage(),
new expo.modules.sqlite.SQLitePackage(),
new expo.modules.webbrowser.WebBrowserPackage()
);
}
}

View file

@ -1,8 +1,5 @@
rootProject.name = 'Campus' rootProject.name = 'Campus'
apply from: '../node_modules/react-native-unimodules/gradle.groovy'
includeUnimodulesProjects()
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle");
applyNativeModulesSettingsGradle(settings) applyNativeModulesSettingsGradle(settings)

View file

@ -1,11 +1,3 @@
module.exports = function(api) { module.exports = {
api.cache(true); presets: ['module:metro-react-native-babel-preset'],
return {
presets: ['babel-preset-expo', '@babel/preset-flow'],
env: {
production: {
plugins: ['react-native-paper/babel'],
},
},
};
}; };

View file

@ -1,8 +1,4 @@
import {registerRootComponent} from 'expo'; import {AppRegistry} from 'react-native';
import App from './App'; import App from './App';
// registerRootComponent calls AppRegistry.registerComponent('main', () => App); AppRegistry.registerComponent('main', () => App);
// It also ensures that whether you load the app in the Expo client or in a native build,
// the environment is set up appropriately
registerRootComponent(App);

View file

@ -71,5 +71,9 @@
<string>Automatic</string> <string>Automatic</string>
<key>UIViewControllerBasedStatusBarAppearance</key> <key>UIViewControllerBasedStatusBarAppearance</key>
<false/> <false/>
<key>UIAppFonts</key>
<array>
<string>MaterialCommunityIcons.ttf</string>
</array>
</dict> </dict>
</plist> </plist>

View file

@ -50,4 +50,7 @@ target 'Campus' do
pod 'Permission-Notifications', :path => "#{permissions_path}/Notifications.podspec" pod 'Permission-Notifications', :path => "#{permissions_path}/Notifications.podspec"
pod 'Permission-Camera', :path => "#{permissions_path}/Camera.podspec" pod 'Permission-Camera', :path => "#{permissions_path}/Camera.podspec"
# Vector Icons
pod 'RNVectorIcons', :path => '../node_modules/react-native-vector-icons'
end end

17
metro.config.js Normal file
View file

@ -0,0 +1,17 @@
/**
* Metro configuration for React Native
* https://github.com/facebook/react-native
*
* @format
*/
module.exports = {
transformer: {
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: false,
},
}),
},
};

View file

@ -1,12 +1,13 @@
{ {
"name": "campus",
"version": "2.0.0",
"private": true,
"scripts": { "scripts": {
"start": "react-native start", "start": "react-native start",
"android": "react-native run-android", "android": "react-native run-android",
"ios": "react-native run-ios", "ios": "react-native run-ios",
"web": "expo start --web",
"test": "jest", "test": "jest",
"testw": "jest --watch", "lint": "eslint ."
"testc": "jest --coverage"
}, },
"jest": { "jest": {
"preset": "react-native", "preset": "react-native",
@ -18,15 +19,12 @@
] ]
}, },
"dependencies": { "dependencies": {
"@expo/vector-icons": "^10.0.0", "@nartc/react-native-barcode-mask": "^1.1.9",
"@react-native-community/masked-view": "0.1.6", "@react-native-community/masked-view": "0.1.6",
"@react-navigation/bottom-tabs": "^5.1.1", "@react-navigation/bottom-tabs": "^5.1.1",
"@react-navigation/drawer": "^5.1.1", "@react-navigation/drawer": "^5.1.1",
"@react-navigation/native": "^5.0.9", "@react-navigation/native": "^5.0.9",
"@react-navigation/stack": "^5.1.1", "@react-navigation/stack": "^5.1.1",
"expo": "^37.0.0",
"expo-barcode-scanner": "~8.1.0",
"expo-camera": "latest",
"i18n-js": "^3.3.0", "i18n-js": "^3.3.0",
"react": "~16.9.0", "react": "~16.9.0",
"react-dom": "16.9.0", "react-dom": "16.9.0",
@ -36,6 +34,7 @@
"react-native-appearance": "~0.3.3", "react-native-appearance": "~0.3.3",
"react-native-autolink": "^3.0.0", "react-native-autolink": "^3.0.0",
"react-native-calendars": "^1.260.0", "react-native-calendars": "^1.260.0",
"react-native-camera": "^3.23.1",
"react-native-collapsible": "^1.5.2", "react-native-collapsible": "^1.5.2",
"react-native-gesture-handler": "~1.6.0", "react-native-gesture-handler": "~1.6.0",
"react-native-image-modal": "^1.0.6", "react-native-image-modal": "^1.0.6",
@ -50,22 +49,21 @@
"react-native-safe-area-context": "0.7.3", "react-native-safe-area-context": "0.7.3",
"react-native-screens": "~2.2.0", "react-native-screens": "~2.2.0",
"react-native-splash-screen": "^3.2.0", "react-native-splash-screen": "^3.2.0",
"react-native-unimodules": "~0.9.0", "react-native-vector-icons": "^6.6.0",
"react-native-web": "~0.11.7", "react-native-web": "~0.11.7",
"react-native-webview": "8.1.1", "react-native-webview": "8.1.1",
"react-navigation-collapsible": "^5.5.0", "react-navigation-collapsible": "^5.5.0",
"react-navigation-header-buttons": "^3.0.5" "react-navigation-header-buttons": "^3.0.5"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.9.0", "@babel/core": "^7.6.2",
"babel-jest": "~25.2.6", "@babel/runtime": "^7.6.2",
"jest": "^25.1.0", "@react-native-community/eslint-config": "^0.0.5",
"react-test-renderer": "^16.13.1", "babel-jest": "^24.9.0",
"@babel/cli": "^7.8.4", "eslint": "^6.5.1",
"@babel/preset-flow": "^7.9.0", "jest": "^24.9.0",
"babel-preset-expo": "^8.1.0", "metro-react-native-babel-preset": "^0.58.0",
"flow-bin": "^0.122.0", "react-test-renderer": "16.9.0",
"jest-extended": "^0.11.5" "flow-bin": "^0.122.0"
}, }
"private": true
} }

View file

@ -12,8 +12,7 @@ type Props = {
title: string, title: string,
subtitle?: string, subtitle?: string,
left?: (props: { [keys: string]: any }) => React.Node, left?: (props: { [keys: string]: any }) => React.Node,
startOpen: boolean, opened: boolean,
keepOpen: boolean,
unmountWhenCollapsed: boolean, unmountWhenCollapsed: boolean,
children?: React.Node, children?: React.Node,
} }
@ -24,36 +23,51 @@ type State = {
const AnimatedListIcon = Animatable.createAnimatableComponent(List.Icon); const AnimatedListIcon = Animatable.createAnimatableComponent(List.Icon);
class AnimatedAccordion extends React.PureComponent<Props, State> { class AnimatedAccordion extends React.Component<Props, State> {
static defaultProps = { static defaultProps = {
startOpen: false, opened: false,
keepOpen: false,
unmountWhenCollapsed: false, unmountWhenCollapsed: false,
} }
chevronRef: { current: null | AnimatedListIcon }; chevronRef: { current: null | AnimatedListIcon };
chevronIcon: string;
animStart: string;
animEnd: string;
state = { state = {
expanded: false, expanded: this.props.opened,
} }
constructor(props) { constructor(props) {
super(props); super(props);
this.chevronRef = React.createRef(); this.chevronRef = React.createRef();
this.setupChevron();
} }
componentDidMount() { setupChevron() {
if (this.props.startOpen) if (this.state.expanded) {
this.toggleAccordion(); this.chevronIcon = "chevron-up";
this.animStart = "180deg";
this.animEnd = "0deg";
} else {
this.chevronIcon = "chevron-down";
this.animStart = "0deg";
this.animEnd = "180deg";
}
} }
toggleAccordion = () => { toggleAccordion = () => {
if (!this.props.keepOpen) {
if (this.chevronRef.current != null) if (this.chevronRef.current != null)
this.chevronRef.current.transitionTo({rotate: this.state.expanded ? '0deg' : '180deg'}); this.chevronRef.current.transitionTo({rotate: this.state.expanded ? this.animStart : this.animEnd});
this.setState({expanded: !this.state.expanded}) this.setState({expanded: !this.state.expanded})
}
}; };
shouldComponentUpdate(nextProps: Props) {
this.state.expanded = nextProps.opened;
this.setupChevron();
return true;
}
render() { render() {
const colors = this.props.theme.colors; const colors = this.props.theme.colors;
return ( return (
@ -67,13 +81,13 @@ class AnimatedAccordion extends React.PureComponent<Props, State> {
right={(props) => <AnimatedListIcon right={(props) => <AnimatedListIcon
ref={this.chevronRef} ref={this.chevronRef}
{...props} {...props}
icon={"chevron-down"} icon={this.chevronIcon}
color={this.state.expanded ? colors.primary : undefined} color={this.state.expanded ? colors.primary : undefined}
useNativeDriver useNativeDriver
/>} />}
left={this.props.left} left={this.props.left}
/> />
<Collapsible collapsed={!this.props.keepOpen && !this.state.expanded}> <Collapsible collapsed={!this.state.expanded}>
{!this.props.unmountWhenCollapsed || (this.props.unmountWhenCollapsed && this.state.expanded) {!this.props.unmountWhenCollapsed || (this.props.unmountWhenCollapsed && this.state.expanded)
? this.props.children ? this.props.children
: null} : null}

View file

@ -1,22 +1,43 @@
// @flow // @flow
import * as React from 'react'; import * as React from 'react';
import {Card, List, Text} from 'react-native-paper'; import {Card, Chip, List, Text} from 'react-native-paper';
import {StyleSheet, View} from "react-native"; import {StyleSheet, View} from "react-native";
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import AnimatedAccordion from "../../Animations/AnimatedAccordion"; import AnimatedAccordion from "../../Animations/AnimatedAccordion";
import {isItemInCategoryFilter} from "../../../utils/Search";
import type {category} from "../../../screens/Amicale/Clubs/ClubListScreen";
type Props = { type Props = {
categoryRender: Function, categories: Array<category>,
categories: Array<Object>, onChipSelect: (id: number) => void,
selectedCategories: Array<number>,
} }
class ClubListHeader extends React.Component<Props> { class ClubListHeader extends React.Component<Props> {
shouldComponentUpdate(nextProps: Props) {
return nextProps.selectedCategories.length !== this.props.selectedCategories.length;
}
getChipRender = (category: category, key: string) => {
const onPress = () => this.props.onChipSelect(category.id);
return <Chip
selected={isItemInCategoryFilter(this.props.selectedCategories, [category.id])}
mode={'outlined'}
onPress={onPress}
style={{marginRight: 5, marginBottom: 5}}
key={key}
>
{category.name}
</Chip>;
};
getCategoriesRender() { getCategoriesRender() {
let final = []; let final = [];
for (let i = 0; i < this.props.categories.length; i++) { for (let i = 0; i < this.props.categories.length; i++) {
final.push(this.props.categoryRender(this.props.categories[i], this.props.categories[i].id)); final.push(this.getChipRender(this.props.categories[i], this.props.categories[i].id.toString()));
} }
return final; return final;
} }
@ -27,7 +48,7 @@ class ClubListHeader extends React.Component<Props> {
<AnimatedAccordion <AnimatedAccordion
title={i18n.t("clubs.categories")} title={i18n.t("clubs.categories")}
left={props => <List.Icon {...props} icon="star"/>} left={props => <List.Icon {...props} icon="star"/>}
startOpen={true} opened={true}
> >
<Text style={styles.text}>{i18n.t("clubs.categoriesFilterMessage")}</Text> <Text style={styles.text}>{i18n.t("clubs.categoriesFilterMessage")}</Text>
<View style={styles.chipContainer}> <View style={styles.chipContainer}>

View file

@ -19,27 +19,16 @@ type Props = {
theme: CustomTheme, theme: CustomTheme,
} }
type State = {
expanded: boolean,
}
const LIST_ITEM_HEIGHT = 64; const LIST_ITEM_HEIGHT = 64;
class GroupListAccordion extends React.Component<Props, State> { class GroupListAccordion extends React.Component<Props> {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = {
expanded: props.item.id === "0",
}
} }
shouldComponentUpdate(nextProps: Props, nextSate: State) { shouldComponentUpdate(nextProps: Props) {
if (nextProps.currentSearchString !== this.props.currentSearchString)
this.state.expanded = nextProps.currentSearchString.length > 0;
return (nextProps.currentSearchString !== this.props.currentSearchString) return (nextProps.currentSearchString !== this.props.currentSearchString)
|| (nextSate.expanded !== this.state.expanded)
|| (nextProps.favoriteNumber !== this.props.favoriteNumber) || (nextProps.favoriteNumber !== this.props.favoriteNumber)
|| (nextProps.item.content.length !== this.props.item.content.length); || (nextProps.item.content.length !== this.props.item.content.length);
} }
@ -61,8 +50,6 @@ class GroupListAccordion extends React.Component<Props, State> {
return null; return null;
} }
itemLayout = (data, index) => ({length: LIST_ITEM_HEIGHT, offset: LIST_ITEM_HEIGHT * index, index});
render() { render() {
const item = this.props.item; const item = this.props.item;
return ( return (
@ -82,6 +69,7 @@ class GroupListAccordion extends React.Component<Props, State> {
/> />
: null} : null}
unmountWhenCollapsed={true}// Only render list if expanded for increased performance unmountWhenCollapsed={true}// Only render list if expanded for increased performance
opened={this.props.item.id === 0 || this.props.currentSearchString.length > 0}
> >
{/*$FlowFixMe*/} {/*$FlowFixMe*/}
<FlatList <FlatList
@ -91,8 +79,8 @@ class GroupListAccordion extends React.Component<Props, State> {
keyExtractor={this.keyExtractor} keyExtractor={this.keyExtractor}
listKey={item.id.toString()} listKey={item.id.toString()}
// Performance props, see https://reactnative.dev/docs/optimizing-flatlist-configuration // Performance props, see https://reactnative.dev/docs/optimizing-flatlist-configuration
getItemLayout={this.itemLayout} // Broken with search // getItemLayout={this.itemLayout} // Broken with search
removeClippedSubviews={true} // removeClippedSubviews={true}
/> />
</AnimatedAccordion> </AnimatedAccordion>
</View> </View>

View file

@ -1,7 +1,7 @@
// @flow // @flow
import * as React from 'react'; import * as React from 'react';
import {MaterialCommunityIcons} from "@expo/vector-icons"; import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons";
import {HeaderButton, HeaderButtons} from 'react-navigation-header-buttons'; import {HeaderButton, HeaderButtons} from 'react-navigation-header-buttons';
import {withTheme} from "react-native-paper"; import {withTheme} from "react-native-paper";
import * as Touchable from "react-native/Libraries/Components/Touchable/TouchableNativeFeedback.android"; import * as Touchable from "react-native/Libraries/Components/Touchable/TouchableNativeFeedback.android";

View file

@ -2,7 +2,7 @@
import * as React from 'react'; import * as React from 'react';
import {Image, Platform, StatusBar, StyleSheet, View} from "react-native"; import {Image, Platform, StatusBar, StyleSheet, View} from "react-native";
import {MaterialCommunityIcons} from "@expo/vector-icons"; import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons";
import {Text} from "react-native-paper"; import {Text} from "react-native-paper";
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import AppIntroSlider from "react-native-app-intro-slider"; import AppIntroSlider from "react-native-app-intro-slider";

View file

@ -3,7 +3,7 @@
import * as React from 'react'; import * as React from 'react';
import {Button, Subheading, withTheme} from 'react-native-paper'; import {Button, Subheading, withTheme} from 'react-native-paper';
import {StyleSheet, View} from "react-native"; import {StyleSheet, View} from "react-native";
import {MaterialCommunityIcons} from "@expo/vector-icons"; import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons";
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import {ERROR_TYPE} from "../../utils/WebData"; import {ERROR_TYPE} from "../../utils/WebData";
import * as Animatable from 'react-native-animatable'; import * as Animatable from 'react-native-animatable';

View file

@ -3,7 +3,7 @@
import * as React from 'react'; import * as React from 'react';
import {View} from "react-native"; import {View} from "react-native";
import {TouchableRipple, withTheme} from 'react-native-paper'; import {TouchableRipple, withTheme} from 'react-native-paper';
import {MaterialCommunityIcons} from "@expo/vector-icons"; import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons";
import * as Animatable from "react-native-animatable"; import * as Animatable from "react-native-animatable";
type Props = { type Props = {

View file

@ -23,13 +23,15 @@ import ServicesSectionScreen from "../screens/Services/ServicesSectionScreen";
import AmicaleContactScreen from "../screens/Amicale/AmicaleContactScreen"; import AmicaleContactScreen from "../screens/Amicale/AmicaleContactScreen";
import {createScreenCollapsibleStack, getWebsiteStack} from "../utils/CollapsibleUtils"; import {createScreenCollapsibleStack, getWebsiteStack} from "../utils/CollapsibleUtils";
const modalTransition = Platform.OS === 'ios' ? TransitionPresets.ModalPresentationIOS : TransitionPresets.ModalSlideFromBottomIOS;
const defaultScreenOptions = { const defaultScreenOptions = {
gestureEnabled: true, gestureEnabled: true,
cardOverlayEnabled: true, cardOverlayEnabled: true,
...TransitionPresets.ScaleFromCenterAndroid, ...modalTransition,
}; };
const modalTransition = Platform.OS === 'ios' ? TransitionPresets.ModalPresentationIOS : TransitionPresets.ModalSlideFromBottomIOS;
const ServicesStack = createStackNavigator(); const ServicesStack = createStackNavigator();
@ -42,7 +44,7 @@ function ServicesStackComponent() {
> >
{createScreenCollapsibleStack("index", ServicesStack, WebsitesHomeScreen, i18n.t('screens.services'))} {createScreenCollapsibleStack("index", ServicesStack, WebsitesHomeScreen, i18n.t('screens.services'))}
{createScreenCollapsibleStack("services-section", ServicesStack, ServicesSectionScreen, "SECTION")} {createScreenCollapsibleStack("services-section", ServicesStack, ServicesSectionScreen, "SECTION")}
{createScreenCollapsibleStack("amicale-contact", ServicesStack, AmicaleContactScreen, i18n.t('screens.amicaleAbout'), true, {...modalTransition})} {createScreenCollapsibleStack("amicale-contact", ServicesStack, AmicaleContactScreen, i18n.t('screens.amicaleAbout'))}
</ServicesStack.Navigator> </ServicesStack.Navigator>
); );
} }
@ -60,10 +62,7 @@ function ProxiwashStackComponent() {
<ProxiwashStack.Screen <ProxiwashStack.Screen
name="proxiwash-about" name="proxiwash-about"
component={ProxiwashAboutScreen} component={ProxiwashAboutScreen}
options={{ options={{title: i18n.t('screens.proxiwash'),}}
title: i18n.t('screens.proxiwash'),
...modalTransition,
}}
/> />
</ProxiwashStack.Navigator> </ProxiwashStack.Navigator>
); );
@ -79,19 +78,14 @@ function PlanningStackComponent() {
screenOptions={defaultScreenOptions} screenOptions={defaultScreenOptions}
> >
<PlanningStack.Screen <PlanningStack.Screen
name="planning" name="index"
component={PlanningScreen} component={PlanningScreen}
options={{ options={{title: i18n.t('screens.planning'),}}
title: i18n.t('screens.planning'),
}}
/> />
<PlanningStack.Screen <PlanningStack.Screen
name="planning-information" name="planning-information"
component={PlanningDisplayScreen} component={PlanningDisplayScreen}
options={{ options={{title: i18n.t('screens.planningDisplayScreen'),}}
title: i18n.t('screens.planningDisplayScreen'),
...modalTransition,
}}
/> />
</PlanningStack.Navigator> </PlanningStack.Navigator>
); );
@ -130,34 +124,22 @@ function HomeStackComponent(initialRoute: string | null, defaultData: { [key: st
<HomeStack.Screen <HomeStack.Screen
name="scanner" name="scanner"
component={ScannerScreen} component={ScannerScreen}
options={{ options={{title: i18n.t('screens.scanner'),}}
title: i18n.t('screens.scanner'),
...modalTransition,
}}
/> />
<HomeStack.Screen <HomeStack.Screen
name="club-information" name="club-information"
component={ClubDisplayScreen} component={ClubDisplayScreen}
options={{ options={{title: i18n.t('screens.clubDisplayScreen'),}}
title: i18n.t('screens.clubDisplayScreen'),
...modalTransition,
}}
/> />
<HomeStack.Screen <HomeStack.Screen
name="feed-information" name="feed-information"
component={FeedItemScreen} component={FeedItemScreen}
options={{ options={{title: i18n.t('screens.feedDisplayScreen'),}}
title: i18n.t('screens.feedDisplayScreen'),
...modalTransition,
}}
/> />
<HomeStack.Screen <HomeStack.Screen
name="planning-information" name="planning-information"
component={PlanningDisplayScreen} component={PlanningDisplayScreen}
options={{ options={{title: i18n.t('screens.planningDisplayScreen'),}}
title: i18n.t('screens.planningDisplayScreen'),
...modalTransition,
}}
/> />
</HomeStack.Navigator> </HomeStack.Navigator>
); );
@ -173,13 +155,7 @@ function PlanexStackComponent() {
screenOptions={defaultScreenOptions} screenOptions={defaultScreenOptions}
> >
{getWebsiteStack("index", PlanexStack, PlanexScreen, "Planex")} {getWebsiteStack("index", PlanexStack, PlanexScreen, "Planex")}
{createScreenCollapsibleStack( {createScreenCollapsibleStack("group-select", PlanexStack, GroupSelectionScreen, "GROUP SELECT")}
"group-select",
PlanexStack,
GroupSelectionScreen,
"GROUP SELECT",
true,
{...modalTransition})}
</PlanexStack.Navigator> </PlanexStack.Navigator>
); );
} }

View file

@ -2,7 +2,7 @@
import * as React from 'react'; import * as React from 'react';
import {Animated, Platform} from "react-native"; import {Animated, Platform} from "react-native";
import {Chip, Searchbar} from 'react-native-paper'; import {Searchbar} from 'react-native-paper';
import AuthenticatedScreen from "../../../components/Amicale/AuthenticatedScreen"; import AuthenticatedScreen from "../../../components/Amicale/AuthenticatedScreen";
import i18n from "i18n-js"; import i18n from "i18n-js";
import ClubListItem from "../../../components/Lists/Clubs/ClubListItem"; import ClubListItem from "../../../components/Lists/Clubs/ClubListItem";
@ -36,7 +36,7 @@ type Props = {
} }
type State = { type State = {
currentlySelectedCategories: Array<string>, currentlySelectedCategories: Array<number>,
currentSearchString: string, currentSearchString: string,
} }
@ -99,13 +99,11 @@ class ClubListScreen extends React.Component<Props, State> {
this.updateFilteredData(str, null); this.updateFilteredData(str, null);
}; };
keyExtractor = (item: club) => { keyExtractor = (item: club) => item.id.toString();
return item.id.toString();
};
itemLayout = (data, index) => ({length: LIST_ITEM_HEIGHT, offset: LIST_ITEM_HEIGHT * index, index}); itemLayout = (data, index) => ({length: LIST_ITEM_HEIGHT, offset: LIST_ITEM_HEIGHT * index, index});
getScreen = (data: Array<{categories: Array<category>, clubs: Array<club>} | null>) => { getScreen = (data: Array<{ categories: Array<category>, clubs: Array<club> } | null>) => {
let categoryList = []; let categoryList = [];
let clubList = []; let clubList = [];
if (data[0] != null) { if (data[0] != null) {
@ -131,11 +129,9 @@ class ClubListScreen extends React.Component<Props, State> {
) )
}; };
onChipSelect(id: string) { onChipSelect = (id: number) => this.updateFilteredData(null, id);
this.updateFilteredData(null, id);
}
updateFilteredData(filterStr: string | null, categoryId: string | null) { updateFilteredData(filterStr: string | null, categoryId: number | null) {
let newCategoriesState = [...this.state.currentlySelectedCategories]; let newCategoriesState = [...this.state.currentlySelectedCategories];
let newStrState = this.state.currentSearchString; let newStrState = this.state.currentSearchString;
if (filterStr !== null) if (filterStr !== null)
@ -154,23 +150,11 @@ class ClubListScreen extends React.Component<Props, State> {
}) })
} }
getChipRender = (category: category, key: string) => {
const onPress = this.onChipSelect.bind(this, category.id);
return <Chip
selected={isItemInCategoryFilter(this.state.currentlySelectedCategories, [category.id])}
mode={'outlined'}
onPress={onPress}
style={{marginRight: 5, marginBottom: 5}}
key={key}
>
{category.name}
</Chip>;
};
getListHeader() { getListHeader() {
return <ClubListHeader return <ClubListHeader
categories={this.categories} categories={this.categories}
categoryRender={this.getChipRender} selectedCategories={this.state.currentlySelectedCategories}
onChipSelect={this.onChipSelect}
/>; />;
} }
@ -189,7 +173,7 @@ class ClubListScreen extends React.Component<Props, State> {
return shouldRender; return shouldRender;
} }
getRenderItem = ({item}: {item: club}) => { getRenderItem = ({item}: { item: club }) => {
const onPress = this.onListItemPress.bind(this, item); const onPress = this.onListItemPress.bind(this, item);
if (this.shouldRenderItem(item)) { if (this.shouldRenderItem(item)) {
return ( return (

View file

@ -1,15 +1,16 @@
// @flow // @flow
import * as React from 'react'; import * as React from 'react';
import {Linking, StyleSheet, View} from "react-native"; import {Linking, Platform, StyleSheet, View} from "react-native";
import {Button, Text, withTheme} from 'react-native-paper'; import {Button, Text, withTheme} from 'react-native-paper';
import {BarCodeScanner} from "expo-barcode-scanner"; import {RNCamera} from 'react-native-camera';
import {Camera} from 'expo-camera'; import {BarcodeMask} from '@nartc/react-native-barcode-mask';
import URLHandler from "../../utils/URLHandler"; import URLHandler from "../../utils/URLHandler";
import AlertDialog from "../../components/Dialogs/AlertDialog"; import AlertDialog from "../../components/Dialogs/AlertDialog";
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import CustomTabBar from "../../components/Tabbar/CustomTabBar"; import CustomTabBar from "../../components/Tabbar/CustomTabBar";
import LoadingConfirmDialog from "../../components/Dialogs/LoadingConfirmDialog"; import LoadingConfirmDialog from "../../components/Dialogs/LoadingConfirmDialog";
import {PERMISSIONS, request, RESULTS} from 'react-native-permissions';
type Props = {}; type Props = {};
type State = { type State = {
@ -40,9 +41,14 @@ class ScannerScreen extends React.Component<Props, State> {
this.requestPermissions(); this.requestPermissions();
} }
requestPermissions = () => Camera.requestPermissionsAsync().then(this.updatePermissionStatus); requestPermissions = () => {
if (Platform.OS === 'android')
request(PERMISSIONS.ANDROID.CAMERA).then(this.updatePermissionStatus)
else
request(PERMISSIONS.IOS.CAMERA).then(this.updatePermissionStatus)
};
updatePermissionStatus = ({status}) => this.setState({hasPermission: status === "granted"}); updatePermissionStatus = (result) => this.setState({hasPermission: result === RESULTS.GRANTED});
handleCodeScanned = ({type, data}) => { handleCodeScanned = ({type, data}) => {
if (!URLHandler.isUrlValid(data)) if (!URLHandler.isUrlValid(data))
@ -71,36 +77,6 @@ class ScannerScreen extends React.Component<Props, State> {
</View> </View>
} }
getOverlay() {
return (
<View style={{flex: 1}}>
<View style={{flex: 1}}>
<View style={{...overlayBackground, top: 0, height: '10%', width: '80%', left: '10%'}}/>
<View style={{...overlayBackground, left: 0, width: '10%', height: '100%'}}/>
<View style={{...overlayBackground, right: 0, width: '10%', height: '100%'}}/>
<View style={{...overlayBackground, bottom: 0, height: '10%', width: '80%', left: '10%'}}/>
</View>
<View style={styles.overlayTopLeft}>
<View style={{...overlayHorizontalLineStyle, top: 0}}/>
<View style={{...overlayVerticalLineStyle, left: 0}}/>
</View>
<View style={styles.overlayTopRight}>
<View style={{...overlayHorizontalLineStyle, top: 0}}/>
<View style={{...overlayVerticalLineStyle, right: 0}}/>
</View>
<View style={styles.overlayBottomLeft}>
<View style={{...overlayHorizontalLineStyle, bottom: 0}}/>
<View style={{...overlayVerticalLineStyle, left: 0}}/>
</View>
<View style={styles.overlayBottomRight}>
<View style={{...overlayHorizontalLineStyle, bottom: 0}}/>
<View style={{...overlayVerticalLineStyle, right: 0}}/>
</View>
</View>
);
}
showHelpDialog = () => { showHelpDialog = () => {
this.setState({ this.setState({
dialogVisible: true, dialogVisible: true,
@ -133,19 +109,22 @@ class ScannerScreen extends React.Component<Props, State> {
getScanner() { getScanner() {
return ( return (
<View style={styles.cameraContainer}> <RNCamera
<Camera onBarCodeRead={this.state.scanned ? undefined : this.handleCodeScanned}
onBarCodeScanned={this.state.scanned ? undefined : this.handleCodeScanned} type={RNCamera.Constants.Type.back}
type={Camera.Constants.Type.back}
barCodeScannerSettings={{ barCodeScannerSettings={{
barCodeTypes: [BarCodeScanner.Constants.BarCodeType.qr], barCodeTypes: [RNCamera.Constants.BarCodeType.qr],
}} }}
style={StyleSheet.absoluteFill} style={StyleSheet.absoluteFill}
ratio={'1:1'} captureAudio={false}
> >
{this.getOverlay()} <BarcodeMask
</Camera> backgroundColor={"#000"}
</View> maskOpacity={0.5}
animatedLineThickness={1}
animationDuration={1000}
/>
</RNCamera>
); );
} }
@ -153,13 +132,12 @@ class ScannerScreen extends React.Component<Props, State> {
return ( return (
<View style={{ <View style={{
...styles.container, ...styles.container,
marginBottom: CustomTabBar.TAB_BAR_HEIGHT + 20 marginBottom: CustomTabBar.TAB_BAR_HEIGHT
}}> }}>
{this.state.hasPermission {this.state.hasPermission
? this.getScanner() ? this.getScanner()
: this.getPermissionScreen() : this.getPermissionScreen()
} }
<View style={{height: 50}}>
<Button <Button
icon="information" icon="information"
mode="contained" mode="contained"
@ -168,7 +146,6 @@ class ScannerScreen extends React.Component<Props, State> {
> >
{i18n.t("scannerScreen.helpButton")} {i18n.t("scannerScreen.helpButton")}
</Button> </Button>
</View>
<AlertDialog <AlertDialog
visible={this.state.dialogVisible} visible={this.state.dialogVisible}
onDismiss={this.onDialogDismiss} onDismiss={this.onDialogDismiss}
@ -185,74 +162,17 @@ class ScannerScreen extends React.Component<Props, State> {
} }
} }
const borderOffset = '10%';
const overlayBoxStyle = {
position: 'absolute',
width: 25,
height: 25,
};
const overlayLineStyle = {
position: 'absolute',
backgroundColor: "#fff",
borderRadius: 2,
};
const overlayHorizontalLineStyle = {
...overlayLineStyle,
width: '100%',
height: 5,
};
const overlayVerticalLineStyle = {
...overlayLineStyle,
height: '100%',
width: 5,
};
const overlayBackground = {
backgroundColor: "rgba(0,0,0,0.47)",
position: "absolute",
};
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
flex: 1, flex: 1,
justifyContent: 'center', justifyContent: 'center',
}, },
cameraContainer: {
marginTop: 'auto',
marginBottom: 'auto',
aspectRatio: 1,
width: '100%',
},
button: { button: {
position: 'absolute', position: 'absolute',
bottom: 5, bottom: 20,
width: '80%', width: '80%',
left: '10%' left: '10%'
}, },
overlayTopLeft: {
...overlayBoxStyle,
top: borderOffset,
left: borderOffset,
},
overlayTopRight: {
...overlayBoxStyle,
top: borderOffset,
right: borderOffset,
},
overlayBottomLeft: {
...overlayBoxStyle,
bottom: borderOffset,
left: borderOffset,
},
overlayBottomRight: {
...overlayBoxStyle,
bottom: borderOffset,
right: borderOffset,
},
}); });
export default withTheme(ScannerScreen); export default withTheme(ScannerScreen);

View file

@ -36,7 +36,6 @@ class FeedbackScreen extends React.Component<Props> {
}}> }}>
<Button <Button
icon="email" icon="email"
mode="contained"
style={{ style={{
marginLeft: 'auto', marginLeft: 'auto',
marginTop: 5, marginTop: 5,
@ -46,7 +45,6 @@ class FeedbackScreen extends React.Component<Props> {
</Button> </Button>
<Button <Button
icon="git" icon="git"
mode="contained"
style={{ style={{
marginLeft: 'auto', marginLeft: 'auto',
marginTop: 5, marginTop: 5,
@ -56,7 +54,6 @@ class FeedbackScreen extends React.Component<Props> {
</Button> </Button>
<Button <Button
icon="facebook" icon="facebook"
mode="contained"
style={{ style={{
marginLeft: 'auto', marginLeft: 'auto',
marginTop: 5, marginTop: 5,

View file

@ -1,12 +1,12 @@
// @flow // @flow
import * as React from 'react'; import * as React from 'react';
import {Alert, Platform, View} from 'react-native'; import {Alert, View} from 'react-native';
import i18n from "i18n-js"; import i18n from "i18n-js";
import WebSectionList from "../../components/Screens/WebSectionList"; import WebSectionList from "../../components/Screens/WebSectionList";
import * as Notifications from "../../utils/Notifications"; import * as Notifications from "../../utils/Notifications";
import AsyncStorageManager from "../../managers/AsyncStorageManager"; import AsyncStorageManager from "../../managers/AsyncStorageManager";
import * as Expo from "expo"; // import * as Expo from "expo";
import {Avatar, Banner, Button, Card, Text, withTheme} from 'react-native-paper'; import {Avatar, Banner, Button, Card, Text, withTheme} from 'react-native-paper';
import ProxiwashListItem from "../../components/Lists/Proxiwash/ProxiwashListItem"; import ProxiwashListItem from "../../components/Lists/Proxiwash/ProxiwashListItem";
import ProxiwashConstants from "../../constants/ProxiwashConstants"; import ProxiwashConstants from "../../constants/ProxiwashConstants";
@ -92,18 +92,18 @@ class ProxiwashScreen extends React.Component<Props, State> {
this.setState({machinesWatched: fetchedList}) this.setState({machinesWatched: fetchedList})
}); });
// Get updated watchlist after received notification // Get updated watchlist after received notification
Expo.Notifications.addListener(() => { // Expo.Notifications.addListener(() => {
Notifications.getMachineNotificationWatchlist((fetchedList) => { // Notifications.getMachineNotificationWatchlist((fetchedList) => {
this.setState({machinesWatched: fetchedList}) // this.setState({machinesWatched: fetchedList})
}); // });
}); // });
if (Platform.OS === 'android') { // if (Platform.OS === 'android') {
Expo.Notifications.createChannelAndroidAsync('reminders', { // Expo.Notifications.createChannelAndroidAsync('reminders', {
name: 'Reminders', // name: 'Reminders',
priority: 'max', // priority: 'max',
vibrate: [0, 250, 250, 250], // vibrate: [0, 250, 250, 250],
}); // });
} // }
} }
} }

View file

@ -61,7 +61,7 @@ class ProximoListScreen extends React.Component<Props, State> {
constructor(props) { constructor(props) {
super(props); super(props);
this.listData = this.props.route.params['data']['data']; this.listData = this.props.route.params['data']['data'].sort(sortName);
this.shouldFocusSearchBar = this.props.route.params['shouldFocusSearchBar']; this.shouldFocusSearchBar = this.props.route.params['shouldFocusSearchBar'];
this.state = { this.state = {
currentSearchString: '', currentSearchString: '',

View file

@ -3,7 +3,7 @@
import * as React from 'react'; import * as React from 'react';
import {Alert, View} from 'react-native'; import {Alert, View} from 'react-native';
import {IconButton, Text, withTheme} from 'react-native-paper'; import {IconButton, Text, withTheme} from 'react-native-paper';
import {MaterialCommunityIcons} from "@expo/vector-icons"; import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons";
import GameLogic from "./GameLogic"; import GameLogic from "./GameLogic";
import Grid from "./components/Grid"; import Grid from "./components/Grid";
import Preview from "./components/Preview"; import Preview from "./components/Preview";

View file

@ -1,7 +1,7 @@
// @flow // @flow
import {checkNotifications, requestNotifications, RESULTS} from 'react-native-permissions'; import {checkNotifications, requestNotifications, RESULTS} from 'react-native-permissions';
import {Notifications} from 'expo'; // import {Notifications} from 'expo';
import AsyncStorageManager from "../managers/AsyncStorageManager"; import AsyncStorageManager from "../managers/AsyncStorageManager";
import LocaleManager from "../managers/LocaleManager"; import LocaleManager from "../managers/LocaleManager";
import passwords from "../../passwords"; import passwords from "../../passwords";
@ -40,15 +40,15 @@ export async function askPermissions() {
* @return {Promise<void>} * @return {Promise<void>}
*/ */
export async function initExpoToken() { export async function initExpoToken() {
let token = AsyncStorageManager.getInstance().preferences.expoToken.current; // let token = AsyncStorageManager.getInstance().preferences.expoToken.current;
if (token === '') { // if (token === '') {
askPermissions().then(() => { // askPermissions().then(() => {
Notifications.getExpoPushTokenAsync().then((token) => { // Notifications.getExpoPushTokenAsync().then((token) => {
// Save token for instant use later on // // Save token for instant use later on
AsyncStorageManager.getInstance().savePref(AsyncStorageManager.getInstance().preferences.expoToken.key, token); // AsyncStorageManager.getInstance().savePref(AsyncStorageManager.getInstance().preferences.expoToken.key, token);
}); // });
}); // });
} // }
} }
/** /**