diff --git a/.circleci/config.yml b/.circleci/config.yml index edf2dcb14..1be1e3871 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -150,7 +150,7 @@ commands: if [[ $CIRCLE_JOB == "android-build-official" ]]; then ./gradlew bundleOfficialPlayRelease fi - if [[ $CIRCLE_JOB == "android-build-experimental" ]]; then + if [[ $CIRCLE_JOB == "android-build-experimental" || "android-automatic-build-experimental" ]]; then ./gradlew bundleExperimentalPlayRelease fi if [[ ! $KEYSTORE ]]; then @@ -169,7 +169,7 @@ commands: --source-map=android/app/build/generated/sourcemaps/react/officialPlay/release/app.bundle.map \ --bundle android/app/build/generated/assets/react/officialPlay/release/app.bundle fi - if [[ $CIRCLE_JOB == "android-build-experimental" ]]; then + if [[ $CIRCLE_JOB == "android-build-experimental" || "android-automatic-build-experimental" ]]; then npx bugsnag-source-maps upload-react-native \ --api-key=$BUGSNAG_KEY \ --app-version-code=$CIRCLE_BUILD_NUM \ @@ -379,7 +379,19 @@ jobs: resource_class: large steps: - android-build - + + # Android automatic builds + android-automatic-build-experimental: + <<: *defaults + docker: + - image: circleci/android:api-29-node + environment: + <<: *android-env + <<: *bash-env + resource_class: large + steps: + - android-build + android-build-official: <<: *defaults docker: @@ -485,6 +497,10 @@ workflows: type: approval requires: - lint-testunit + filters: + branches: + ignore: + - develop - android-build-experimental: requires: - android-hold-build-experimental @@ -505,7 +521,7 @@ workflows: - android-google-play-production-experimental: requires: - android-hold-google-play-production-experimental - + # Android Official - android-hold-build-official: type: approval @@ -521,3 +537,15 @@ workflows: - android-google-play-beta-official: requires: - android-hold-google-play-beta-official + + # Android Automatic Experimental + - android-automatic-build-experimental: + filters: + branches: + only: + - develop + requires: + - lint-testunit + - android-google-play-production-experimental: + requires: + - android-automatic-build-experimental diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..880b92a78 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "daily" + open-pull-requests-limit: 25 \ No newline at end of file diff --git a/android/app/build.gradle b/android/app/build.gradle index 2f8a6ef6c..a44f99069 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -144,7 +144,7 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode VERSIONCODE as Integer - versionName "4.27.1" + versionName "4.28.0" vectorDrawables.useSupportLibrary = true if (!isFoss) { manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String] diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 312cce983..40d363a85 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.9.2-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists \ No newline at end of file diff --git a/app/actions/createDiscussion.ts b/app/actions/createDiscussion.ts index bf64baaa6..d9d9690a5 100644 --- a/app/actions/createDiscussion.ts +++ b/app/actions/createDiscussion.ts @@ -2,8 +2,16 @@ import { Action } from 'redux'; import { CREATE_DISCUSSION } from './actionsTypes'; +export interface ICreateDiscussionRequestData { + prid: string; + pmid?: string; + t_name?: string; + reply?: string; + users: string[]; + encrypted?: boolean; +} interface ICreateDiscussionRequest extends Action { - data: any; + data: ICreateDiscussionRequestData; } interface ICreateDiscussionSuccess extends Action { diff --git a/app/actions/customEmojis.ts b/app/actions/customEmojis.ts index 261fbd241..a870d21fb 100644 --- a/app/actions/customEmojis.ts +++ b/app/actions/customEmojis.ts @@ -1,7 +1,7 @@ import { Action } from 'redux'; -import { ICustomEmojis } from '../reducers/customEmojis'; import { SET_CUSTOM_EMOJIS } from './actionsTypes'; +import { ICustomEmojis } from '../definitions'; export interface ISetCustomEmojis extends Action { emojis: ICustomEmojis; diff --git a/app/containers/ActionSheet/ActionSheet.tsx b/app/containers/ActionSheet/ActionSheet.tsx index 673e515a1..c028bada6 100644 --- a/app/containers/ActionSheet/ActionSheet.tsx +++ b/app/containers/ActionSheet/ActionSheet.tsx @@ -1,32 +1,21 @@ import { useBackHandler } from '@react-native-community/hooks'; import * as Haptics from 'expo-haptics'; -import React, { forwardRef, isValidElement, useEffect, useImperativeHandle, useRef, useState } from 'react'; -import { Keyboard, Text } from 'react-native'; -import { HandlerStateChangeEventPayload, State, TapGestureHandler } from 'react-native-gesture-handler'; -import Animated, { Easing, Extrapolate, interpolateNode, Value } from 'react-native-reanimated'; +import React, { forwardRef, isValidElement, useEffect, useImperativeHandle, useRef, useState, useCallback } from 'react'; +import { Keyboard } from 'react-native'; +import { Easing } from 'react-native-reanimated'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import ScrollBottomSheet from 'react-native-scroll-bottom-sheet'; +import BottomSheet, { BottomSheetBackdrop } from '@gorhom/bottom-sheet'; -import { themes } from '../../lib/constants'; import { useDimensions, useOrientation } from '../../dimensions'; -import I18n from '../../i18n'; import { useTheme } from '../../theme'; import { isIOS, isTablet } from '../../utils/deviceInfo'; -import * as List from '../List'; -import { Button } from './Button'; import { Handle } from './Handle'; -import { IActionSheetItem, Item } from './Item'; -import { TActionSheetOptions, TActionSheetOptionsItem } from './Provider'; +import { TActionSheetOptions } from './Provider'; +import BottomSheetContent from './BottomSheetContent'; import styles, { ITEM_HEIGHT } from './styles'; -const getItemLayout = (data: TActionSheetOptionsItem[] | null | undefined, index: number) => ({ - length: ITEM_HEIGHT, - offset: ITEM_HEIGHT * index, - index -}); - const HANDLE_HEIGHT = isIOS ? 40 : 56; -const MAX_SNAP_HEIGHT = 16; +const MIN_SNAP_HEIGHT = 16; const CANCEL_HEIGHT = 64; const ANIMATION_DURATION = 250; @@ -39,27 +28,26 @@ const ANIMATION_CONFIG = { const ActionSheet = React.memo( forwardRef(({ children }: { children: React.ReactElement }, ref) => { - const { theme } = useTheme(); - const bottomSheetRef = useRef>(null); + const { colors } = useTheme(); + const bottomSheetRef = useRef(null); const [data, setData] = useState({} as TActionSheetOptions); const [isVisible, setVisible] = useState(false); const { height } = useDimensions(); const { isLandscape } = useOrientation(); const insets = useSafeAreaInsets(); - const maxSnap = Math.max( - height - - // Items height - ITEM_HEIGHT * (data?.options?.length || 0) - + const maxSnap = Math.min( + // Items height + ITEM_HEIGHT * (data?.options?.length || 0) + // Handle height - HANDLE_HEIGHT - + HANDLE_HEIGHT + // Custom header height - (data?.headerHeight || 0) - + (data?.headerHeight || 0) + // Insets bottom height (Notch devices) - insets.bottom - + insets.bottom + // Cancel button height (data?.hasCancel ? CANCEL_HEIGHT : 0), - MAX_SNAP_HEIGHT + height - MIN_SNAP_HEIGHT ); /* @@ -69,14 +57,12 @@ const ActionSheet = React.memo( * we'll provide more one snap * that point 50% of the whole screen */ - const snaps = height - maxSnap > height * 0.6 && !isLandscape ? [maxSnap, height * 0.5, height] : [maxSnap, height]; - const openedSnapIndex = snaps.length > 2 ? 1 : 0; - const closedSnapIndex = snaps.length - 1; + const snaps = maxSnap > height * 0.6 && !isLandscape && !data.snaps ? [height * 0.5, maxSnap] : [maxSnap]; const toggleVisible = () => setVisible(!isVisible); const hide = () => { - bottomSheetRef.current?.snapTo(closedSnapIndex); + bottomSheetRef.current?.close(); }; const show = (options: TActionSheetOptions) => { @@ -84,12 +70,6 @@ const ActionSheet = React.memo( toggleVisible(); }; - const onBackdropPressed = ({ nativeEvent }: { nativeEvent: HandlerStateChangeEventPayload }) => { - if (nativeEvent.oldState === State.ACTIVE) { - hide(); - } - }; - useBackHandler(() => { if (isVisible) { hide(); @@ -101,7 +81,6 @@ const ActionSheet = React.memo( if (isVisible) { Keyboard.dismiss(); Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); - bottomSheetRef.current?.snapTo(openedSnapIndex); } }, [isVisible]); @@ -122,26 +101,18 @@ const ActionSheet = React.memo( ); - const renderFooter = () => - data?.hasCancel ? ( - - ) : null; - - const renderItem = ({ item }: { item: IActionSheetItem['item'] }) => ; - - const animatedPosition = React.useRef(new Value(0)); - const opacity = interpolateNode(animatedPosition.current, { - inputRange: [0, 1], - outputRange: [0, themes[theme].backdropOpacity], - extrapolate: Extrapolate.CLAMP - }) as any; // The function's return differs from the expected type of opacity, however this problem is something related to lib, maybe when updating the types will be fixed. + const renderBackdrop = useCallback( + props => ( + + ), + [] + ); const bottomSheet = isLandscape || isTablet ? styles.bottomSheet : {}; @@ -149,42 +120,19 @@ const ActionSheet = React.memo( <> {children} {isVisible && ( - <> - - - - - testID='action-sheet' - ref={bottomSheetRef} - componentType='FlatList' - snapPoints={snaps} - initialSnapIndex={closedSnapIndex} - renderHandle={renderHandle} - onSettle={index => index === closedSnapIndex && toggleVisible()} - animatedPosition={animatedPosition.current} - containerStyle={{ ...styles.container, ...bottomSheet, backgroundColor: themes[theme].focusedBackground }} - animationConfig={ANIMATION_CONFIG} - data={data.options} - renderItem={renderItem} - keyExtractor={item => item.title} - style={{ backgroundColor: themes[theme].focusedBackground }} - contentContainerStyle={styles.content} - ItemSeparatorComponent={List.Separator} - ListHeaderComponent={List.Separator} - ListFooterComponent={renderFooter} - getItemLayout={getItemLayout} - removeClippedSubviews={isIOS} - /> - + index === -1 && toggleVisible()}> + + )} ); diff --git a/app/containers/ActionSheet/BottomSheetContent.tsx b/app/containers/ActionSheet/BottomSheetContent.tsx new file mode 100644 index 000000000..ea3b1f1f8 --- /dev/null +++ b/app/containers/ActionSheet/BottomSheetContent.tsx @@ -0,0 +1,59 @@ +import { Text } from 'react-native'; +import React from 'react'; +import { BottomSheetView, BottomSheetFlatList } from '@gorhom/bottom-sheet'; + +import { Button } from './Button'; +import I18n from '../../i18n'; +import { useTheme } from '../../theme'; +import { IActionSheetItem, Item } from './Item'; +import { TActionSheetOptionsItem } from './Provider'; +import styles from './styles'; +import * as List from '../List'; + +interface IBottomSheetContentProps { + hasCancel?: boolean; + options?: TActionSheetOptionsItem[]; + hide: () => void; + children?: React.ReactElement | null; +} + +const BottomSheetContent = React.memo(({ options, hasCancel, hide, children }: IBottomSheetContentProps) => { + const { theme, colors } = useTheme(); + + const renderFooter = () => + hasCancel ? ( + + ) : null; + + const renderItem = ({ item }: { item: IActionSheetItem['item'] }) => ; + + if (options) { + return ( + item.title} + bounces={true} + renderItem={renderItem} + style={{ backgroundColor: colors.focusedBackground }} + keyboardDismissMode='interactive' + indicatorStyle='black' + contentContainerStyle={styles.content} + ItemSeparatorComponent={List.Separator} + ListHeaderComponent={List.Separator} + ListFooterComponent={renderFooter} + /> + ); + } + return {children}; +}); + +export default BottomSheetContent; diff --git a/app/containers/ActionSheet/Item.tsx b/app/containers/ActionSheet/Item.tsx index 1a1627006..2b0b50080 100644 --- a/app/containers/ActionSheet/Item.tsx +++ b/app/containers/ActionSheet/Item.tsx @@ -2,20 +2,14 @@ import React from 'react'; import { Text, View } from 'react-native'; import { themes } from '../../lib/constants'; -import { CustomIcon } from '../../lib/Icons'; +import { CustomIcon } from '../CustomIcon'; import { useTheme } from '../../theme'; import { Button } from './Button'; +import { TActionSheetOptionsItem } from './Provider'; import styles from './styles'; export interface IActionSheetItem { - item: { - title: string; - icon: string; - danger?: boolean; - testID?: string; - onPress: () => void; - right?: Function; - }; + item: TActionSheetOptionsItem; hide(): void; } diff --git a/app/containers/ActionSheet/Provider.tsx b/app/containers/ActionSheet/Provider.tsx index 8ad0e7c27..43f444702 100644 --- a/app/containers/ActionSheet/Provider.tsx +++ b/app/containers/ActionSheet/Provider.tsx @@ -1,14 +1,25 @@ import React, { ForwardedRef, forwardRef, useContext, useRef } from 'react'; import ActionSheet from './ActionSheet'; +import { TIconsName } from '../CustomIcon'; -export type TActionSheetOptionsItem = { title: string; icon: string; onPress: () => void; danger?: boolean }; +export type TActionSheetOptionsItem = { + title: string; + icon: TIconsName; + danger?: boolean; + testID?: string; + onPress: () => void; + right?: () => React.ReactElement; +}; export type TActionSheetOptions = { - options: TActionSheetOptionsItem[]; - headerHeight: number; - customHeader: React.ReactElement | null; + options?: TActionSheetOptionsItem[]; + headerHeight?: number; + customHeader?: React.ReactElement | null; hasCancel?: boolean; + type?: string; + children?: React.ReactElement | null; + snaps?: string[] | number[]; }; interface IActionSheetProvider { showActionSheet: (item: TActionSheetOptions) => void; diff --git a/app/containers/ActionSheet/styles.ts b/app/containers/ActionSheet/styles.ts index 1b9397dc9..68d371ea6 100644 --- a/app/containers/ActionSheet/styles.ts +++ b/app/containers/ActionSheet/styles.ts @@ -46,8 +46,7 @@ export default StyleSheet.create({ }, bottomSheet: { width: '50%', - alignSelf: 'center', - left: '25%' + marginHorizontal: '25%' }, button: { marginHorizontal: 16, diff --git a/app/containers/Button/Button.stories.js b/app/containers/Button/Button.stories.js new file mode 100644 index 000000000..165c8b49b --- /dev/null +++ b/app/containers/Button/Button.stories.js @@ -0,0 +1,28 @@ +import React from 'react'; +import { storiesOf } from '@storybook/react-native'; + +import Button from '.'; + +const buttonProps = { + title: 'Press me!', + type: 'primary', + onPress: () => {}, + testID: 'testButton', + fontSize: 16, + style: { + padding: 10, + justifyContent: 'center' + } +}; + +const stories = storiesOf('Button', module); + +stories.add('primary button', () =>