react-native-safe-area-context#SafeAreaView JavaScript Examples

The following examples show how to use react-native-safe-area-context#SafeAreaView. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: Subscribe.js    From actual with MIT License 6 votes vote down vote up
export function Subscribe({ route, navigation, getUserData, createBudget }) {
  let { email, userId, key } = route.params || {};

  let textStyle = [
    styles.text,
    { fontSize: 17, lineHeight: 25, color: 'white' }
  ];

  return (
    <SafeAreaView style={{ flex: 1 }}>
      <KeyboardAvoidingView>
        {/* <StatusBar barStyle="light-content" /> */}
        <Header navigation={navigation} buttons={['back']} />
        <Stack justify="center" style={{ flex: 1, padding: 20 }} spacing={8}>
          <View>
            <Text style={textStyle}>
              You{"'"}re almost there. You need to subscribe to gain access to
              Actual. No charges will be made for 1 month.
            </Text>
          </View>

          <View style={{ alignItems: 'center' }}>
            <Text style={[textStyle, { fontWeight: '700', marginBottom: 5 }]}>
              Start with a 1 month free trial.
            </Text>
            <AccountButton
              navigation={navigation}
              userData={{ id: userId, key, email }}
              darkMode={true}
              useDummyPurchaser={true}
            />
          </View>
        </Stack>
      </KeyboardAvoidingView>
    </SafeAreaView>
  );
}
Example #2
Source File: ProfileScreen.js    From hero with MIT License 6 votes vote down vote up
ProfileScreen = () => {
  return (
    <SafeAreaView style={styles.appContainer}>
      <StatusBar
        translucent
        backgroundColor="transparent"
        barStyle="dark-content"
      />
      <View style={styles.header}>
        <Text style={styles.appTitle}>profile</Text>
      </View>
      <View style={styles.footer}>
        <Text style={styles.p}>Designed by Gino Lee Swanepoel</Text>
        <Text style={styles.p}>in React Native</Text>
      </View>
    </SafeAreaView>
  );
}
Example #3
Source File: index.js    From musicont with MIT License 6 votes vote down vote up
Index = ({ songs }) => {
	const [assets] = useAssets([require('../../assets/icons/hamburger.png'), require('../../assets/icons/search.png')]);
	const [drawer, setDrawer] = useState(false);

	return (
		<Drawer active={drawer} current="favourite" onItemPressed={() => setDrawer(false)}>
			<SafeAreaView style={styles.container}>
				<Header
					options={{
						left: {
							children: drawer ? <Icon name="x" color="#C4C4C4" /> : <Image source={require('../../assets/icons/hamburger.png')} resizeMode="contain" />,
							onPress: () => setDrawer(!drawer),
						},
						middle: {
							show: true,
							text: 'My Favourites',
						},
						right: {
							show: false,
						},
					}}
				/>
				<View style={styles.sections}>
					{songs && songs.length > 0 ? (
						<Section.MusicList audios={songs} indicator={false} useIndex={true} />
					) : (
						<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
							<Text style={{ fontSize: 24, fontWeight: 'bold', color: 'rgba(0, 0, 0, .3)' }}>No favourite yet!</Text>
						</View>
					)}
				</View>
			</SafeAreaView>
		</Drawer>
	);
}
Example #4
Source File: index.js    From musicont with MIT License 6 votes vote down vote up
Index = () => {
	const [assets] = useAssets([require('../../assets/icons/hamburger.png'), require('../../assets/icons/search.png')]);
	const [drawer, setDrawer] = useState(false);

	return (
		<Drawer active={drawer} current="home" onItemPressed={() => setDrawer(false)}>
			<SafeAreaView style={styles.container}>
				<Header
					options={{
						left: {
							children: drawer ? <Icon name="x" color="#C4C4C4" /> : <Image source={require('../../assets/icons/hamburger.png')} resizeMode="contain" />,
							onPress: () => setDrawer(!drawer),
						},
					}}
				/>
				<View style={styles.sections}>
					<Section.Explore />
					<Section.Recent style={{ marginTop: 30 }} />
					<Section.Playlist style={{ marginTop: 30 }} />
				</View>
				<Footer />
			</SafeAreaView>
		</Drawer>
	);
}
Example #5
Source File: index.js    From musicont with MIT License 6 votes vote down vote up
Index = ({ songs }) => {
	const [assets] = useAssets([require('../../assets/icons/hamburger.png'), require('../../assets/icons/search.png')]);
	const [drawer, setDrawer] = useState(false);

	return (
		<Drawer active={drawer} current="recent" onItemPressed={() => setDrawer(false)}>
			<SafeAreaView style={styles.container}>
				<Header
					options={{
						left: {
							children: drawer ? <Icon name="x" color="#C4C4C4" /> : <Image source={require('../../assets/icons/hamburger.png')} resizeMode="contain" />,
							onPress: () => setDrawer(!drawer),
						},
						middle: {
							show: true,
							text: 'Recently Played',
						},
						right: {
							show: false,
						},
					}}
				/>
				<View style={styles.sections}>
					{songs && songs.length > 0 ? (
						<Section.MusicList audios={songs} indicator={false} useIndex={true} />
					) : (
						<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
							<Text style={{ fontSize: 24, fontWeight: 'bold', color: 'rgba(0, 0, 0, .3)' }}>No recent yet!</Text>
						</View>
					)}
				</View>
			</SafeAreaView>
		</Drawer>
	);
}
Example #6
Source File: index.js    From musicont with MIT License 6 votes vote down vote up
Index = ({ songs }) => {
	const [assets] = useAssets([require('../../assets/icons/hamburger.png'), require('../../assets/icons/search.png')]);
	const [drawer, setDrawer] = useState(false);

	return (
		<Drawer active={drawer} current="songs" onItemPressed={() => setDrawer(false)}>
			<SafeAreaView style={styles.container}>
				<Header
					options={{
						left: {
							children: drawer ? <Icon name="x" color="#C4C4C4" /> : <Image source={require('../../assets/icons/hamburger.png')} resizeMode="contain" />,
							onPress: () => setDrawer(!drawer),
						},
						middle: {
							show: true,
							text: 'All Songs',
						},
						right: {
							show: false,
						},
					}}
				/>
				<View style={styles.sections}>
					<Section.MusicList audios={songs} indicator={false} />
				</View>
			</SafeAreaView>
		</Drawer>
	);
}
Example #7
Source File: SignModal.js    From reddit-clone with MIT License 6 votes vote down vote up
SignModal = ({ navigation }) => {
  const { colors } = useTheme()

  return (
    <TouchableWithoutFeedback onPress={() => navigation.goBack()}>
      <View as={SafeAreaView} style={styles.container}>
        <StatusBar hidden />
        <View
          style={[styles.modal, { backgroundColor: colors.background }]}
          onStartShouldSetResponder={() => true}
        >
          <View style={styles.buttonContainer}>
            <Button
              bgColor={colors.signUpButton}
              title="Sign Up"
              onPress={() => navigation.navigate('SignUp')}
            >
              <PlusCircle color={colors.white} />
            </Button>
            <Button
              bgColor={colors.signInButton}
              title="Sign In"
              onPress={() => navigation.navigate('SignIn')}
            >
              <LogIn color={colors.white} />
            </Button>
          </View>
        </View>
      </View>
    </TouchableWithoutFeedback>
  )
}
Example #8
Source File: App.js    From redis-examples with MIT License 6 votes vote down vote up
render(){
  this.height = Math.round(Dimensions.get('screen').height);
  this.width = Math.round(Dimensions.get('screen').width);
  return (
    <SafeAreaView style={{
      width: this.width,
      height: this.height,
      flex: 1,
      alignItems: 'center'}}>
      <StatusBar
      backgroundColor="#f4511e"/>
    </SafeAreaView>
  );
}
Example #9
Source File: Leaderboard.js    From redis-examples with MIT License 6 votes vote down vote up
render() {
    const {navigate} = this.props.navigation;
    this.height = Math.round(Dimensions.get('screen').height);
    this.width = Math.round(Dimensions.get('screen').width);
    return (
      <SafeAreaView
        style={{
          width: this.width,
          height: this.height,
          flex: 1,
          alignItems: 'center'
        }}>
        <StatusBar
        backgroundColor="#f4511e"/>
        <View
        style={{
          height:this.height,
          width: this.width
        }}>
        <FlatList
          style={{
            flex: 1,
            flexDirection: 'column',
          }}
          renderItem={() => this.renderPlayerItems()}
          data={[{bos: 'boş', key: 'key'}]}
          refreshing={true}></FlatList>
        </View>
      </SafeAreaView>
    );
  }
Example #10
Source File: index.js    From MediBuddy with MIT License 6 votes vote down vote up
Footer = () => (
  <SafeAreaView>
    <Divider />
    <View style={styles.footer}>
      <View style={{ flex: 1 }}>
        <Button
          style={styles.btn}
          labelStyle={styles.cancel}
          mode="text"
          onPress={() => console.log('Pressed')}>
          Cancel
        </Button>
      </View>
      <View style={{ flex: 1 }}>
        <Button
          style={styles.btn}
          labelStyle={styles.ok}
          mode="text"
          onPress={() => console.log('Pressed')}>
          Reschedule
        </Button>
      </View>
    </View>
  </SafeAreaView>
)
Example #11
Source File: HomeScreen.js    From WhatsApp-Clone with MIT License 6 votes vote down vote up
HomeScreen = ({children, style, navigation, ...rest}) => {
  useEffect(() => {
    registerStateChangeListener();
    sendPageLoadStatus()

    return () => {
      // Clean up the subscription
      unRgisterStateChangeListener()
    };
  }, []);

  function registerStateChangeListener() {
    AppState.addEventListener('change', handleAppStateChange);
  }

  function unRgisterStateChangeListener() {
    AppState.removeEventListener('change', handleAppStateChange);
  } 

  return (
    <SafeAreaView style={DEFAULT_STYLES.container}>
      <View style={DEFAULT_STYLES.container}>
        {/* <Header hasTabs style={styles.headerStyle}> */}
        <HomeHeader navigation={navigation} />
        {/* </Header> */}
        <TabView navigation={navigation} />
      </View>
    </SafeAreaView>
  );
}
Example #12
Source File: index.js    From actual with MIT License 5 votes vote down vote up
render() {
    const {
      currentMonth,
      bounds,
      editMode,
      initialized,
      showBudgetDetails
    } = this.state;
    const {
      categories,
      categoryGroups,
      prefs,
      budgetType,
      navigation,
      applyBudgetAction
    } = this.props;
    let numberFormat = prefs.numberFormat || 'comma-dot';

    if (!categoryGroups || !initialized) {
      return (
        <View
          style={{
            flex: 1,
            backgroundColor: 'white',
            alignItems: 'center',
            justifyContent: 'center'
          }}
        >
          <AnimatedLoading width={25} height={25} />
        </View>
      );
    }

    return (
      <SafeAreaView
        edges={['top']}
        style={{ flex: 1, backgroundColor: colors.p5 }}
      >
        <FocusAwareStatusBar barStyle="light-content" />
        <SyncRefresh onSync={this.sync}>
          {({ refreshing, onRefresh }) => (
            <BudgetTable
              // This key forces the whole table rerender when the number
              // format changes
              key={numberFormat}
              categories={categories}
              categoryGroups={categoryGroups}
              type={budgetType}
              month={currentMonth}
              monthBounds={bounds}
              editMode={editMode}
              navigation={navigation}
              refreshControl={
                <RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
              }
              onEditMode={flag => this.setState({ editMode: flag })}
              onShowBudgetDetails={this.onShowBudgetDetails}
              onPrevMonth={this.onPrevMonth}
              onNextMonth={this.onNextMonth}
              onAddCategory={this.onAddCategory}
              onReorderCategory={this.onReorderCategory}
              onReorderGroup={this.onReorderGroup}
              onOpenActionSheet={this.onOpenActionSheet}
              onBudgetAction={applyBudgetAction}
            />
          )}
        </SyncRefresh>

        {showBudgetDetails && (
          <BudgetSummary
            month={currentMonth}
            onClose={() => this.setState({ showBudgetDetails: false })}
          />
        )}
      </SafeAreaView>
    );
  }
Example #13
Source File: BudgetList.js    From actual with MIT License 5 votes vote down vote up
render() {
    let {
      navigation,
      files,
      loadAllFiles,
      getUserData,
      showActionSheetWithOptions,
      keyId
    } = this.props;

    return (
      <SafeAreaView edges={['bottom']} style={{ flex: 1 }}>
        <Modal
          title="Select a file"
          backgroundColor="white"
          allowScrolling={false}
          showOverlay={false}
          edges={['top']}
          rightButton={
            <RefreshButton
              onRefresh={() => {
                getUserData();
                loadAllFiles();
              }}
            />
          }
        >
          {/* <StatusBar barStyle="light-content" /> */}
          <FlatList
            data={files}
            ListEmptyComponent={EmptyMessage}
            renderItem={({ item: file }) => (
              <File
                file={file}
                showActionSheetWithOptions={showActionSheetWithOptions}
                onSelect={() => {
                  if (file.state === 'broken') {
                    showBrokenMessage(file, showActionSheetWithOptions, () =>
                      this.onDelete(file)
                    );
                  } else if (file.state === 'remote') {
                    this.props.downloadBudget(file.cloudFileId);
                  } else {
                    this.props.loadBudget(file.id);
                  }
                }}
                onDelete={this.onDelete}
              />
            )}
            keyExtractor={item => item.id}
            style={{ flex: 1 }}
          />
          <View
            style={{
              alignItems: 'center',
              marginHorizontal: 10,
              marginVertical: 15,
              flexDirection: 'row'
            }}
          >
            <Button primary style={{ flex: 1 }} onPress={() => this.onCreate()}>
              New file
            </Button>
          </View>
        </Modal>
        <UserButton
          navigation={navigation}
          keyId={keyId}
          onLogOut={() => {
            iap.resetUser();
            this.props.signOut();
          }}
        />
      </SafeAreaView>
    );
  }
Example #14
Source File: Login.js    From actual with MIT License 5 votes vote down vote up
function Login({ navigation, createBudget }) {
  let [email, setEmail] = useState('');
  let [loading, setLoading] = useState(false);
  let [error, setError] = useState(null);

  async function sendCode() {
    setLoading(true);
    setError(null);
    let { error } = await send('subscribe-send-email-code', { email });
    setLoading(false);

    if (error) {
      setError(getErrorMessage(error));
    } else {
      navigation.navigate('Confirm', { email });
    }
  }

  let textStyle = [
    styles.text,
    { fontSize: 17, lineHeight: 25, color: 'white' }
  ];

  return (
    <SafeAreaView style={{ flex: 1 }}>
      <KeyboardAvoidingView>
        <TransitionView navigation={navigation}>
          {/* <StatusBar barStyle="light-content" /> */}

          <Header
            navigation={navigation}
            buttons={['back', 'demo']}
            loadDemoBudget={() => {
              send('track', { name: 'app:create-demo' });
              createBudget({ demoMode: true });
            }}
          />

          <Stack justify="center" style={{ flex: 1, padding: 20 }} spacing={5}>
            <View>
              <Text style={textStyle}>
                <Text style={{ fontWeight: '700' }}>Sign in.</Text> We
                {"'"}
                ll email you a code that you can use to log in. You only need to
                do this once. Right now, the mobile app works best as a
                companion to the desktop app.
              </Text>
            </View>

            <SingleInput
              title="Email"
              value={email}
              loading={loading}
              error={error}
              inputProps={{
                keyboardType: 'email-address',
                placeholder: '[email protected]'
              }}
              onChange={setEmail}
              onSubmit={sendCode}
            />
          </Stack>
        </TransitionView>
      </KeyboardAvoidingView>
    </SafeAreaView>
  );
}
Example #15
Source File: index.js    From musicont with MIT License 5 votes vote down vote up
Index = ({ songs, playlists, navigation }) => {
	const [assets] = useAssets([require('../../assets/icons/hamburger.png'), require('../../assets/icons/search.png')]);
	const [drawer, setDrawer] = useState(false);

	return (
		<Drawer active={drawer} current="playlist" onItemPressed={() => setDrawer(false)}>
			<SafeAreaView style={styles.container}>
				<Header
					options={{
						left: {
							children: drawer ? <Icon name="x" color="#C4C4C4" /> : <Image source={require('../../assets/icons/hamburger.png')} resizeMode="contain" />,
							onPress: () => setDrawer(!drawer),
						},
						middle: {
							show: true,
							text: 'Playlists',
						},
						right: {
							show: false,
						},
					}}
				/>
				{playlists && playlists?.length > 0 ? (
					<ScrollView style={{ flex: 1 }} contentContainerStyle={styles.sections} showsVerticalScrollIndicator={false}>
						{playlists.map((playlist, key) => (
							<Card.Playlist
								key={key}
								style={styles.item}
								overlayStyle={{ height: 200 }}
								imageURL={songs[playlist?.songs[0]]?.img}
								title={playlist?.name}
								subtitle={`${playlist?.songs.length} Songs`}
								onPress={() => {
									const playlistIndex = playlists.findIndex((i) => i?.name.toLowerCase() === playlist?.name.toLowerCase());
									navigation.push(SCREENS.PLAYLIST, { playlistIndex });
								}}
							/>
						))}
					</ScrollView>
				) : (
					<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
						<Text style={{ fontSize: 24, fontWeight: 'bold', color: 'rgba(0, 0, 0, .3)' }}>No playlists yet!</Text>
					</View>
				)}
			</SafeAreaView>
		</Drawer>
	);
}
Example #16
Source File: index.js    From musicont with MIT License 5 votes vote down vote up
Index = ({ songs }) => {
	const { goBack } = useNavigation();
	const [audios, setAudios] = useState([]);
	const [search, setSearch] = useState('');

	const handleInput = (val) => {
		const value = val.replace('  ', ' ');
		setSearch(value);
		if (value.length > 3) {
			const results = songs.filter((song) => {
				let regex = new RegExp(value, 'ig');
				return regex.exec(song?.title) || regex.exec(song?.author);
			});

			setAudios(results);
		} else {
			setAudios([]);
		}
	};

	return (
		<>
			<StatusBar style="dark" />
			<TouchableWithoutFeedback onPress={() => Keyboard.dismiss()}>
				<SafeAreaView style={styles.container}>
					<View style={styles.header}>
						<View style={styles.input}>
							<Icon name="search" color="#FFF" />
							<TextInput style={styles.textInput} onChangeText={handleInput} value={search} returnKeyType="search" placeholder="Search..." />
						</View>
						<TouchableOpacity style={styles.btn} onPress={() => goBack()}>
							<Text style={styles.btnTxt}>Cancel</Text>
						</TouchableOpacity>
					</View>
					<View style={styles.result}>
						{audios.length > 0 ? (
							<Section.MusicList audios={audios} />
						) : (
							<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
								<Text style={{ fontSize: 24, fontWeight: 'bold', color: 'rgba(0, 0, 0, .3)' }}>Search something...</Text>
							</View>
						)}
					</View>
				</SafeAreaView>
			</TouchableWithoutFeedback>
		</>
	);
}
Example #17
Source File: Home.js    From reddit-clone with MIT License 5 votes vote down vote up
Home = () => {
  const { authState } = React.useContext(AuthContext)
  const { theme } = React.useContext(ThemeContext)
  const { colors } = useTheme()

  const [postData, setPostData] = React.useState(null)
  const [category, setCategory] = React.useState('all')
  const [isLoading, setIsLoaading] = React.useState(false)

  const getPostData = React.useCallback(async () => {
    setIsLoaading(true)
    const { data } = await axios.get(
      !category || category === 'all' ? 'posts' : `posts/${category}`
    )
    setPostData(data)
    setIsLoaading(false)
  }, [category])

  React.useEffect(() => {
    getPostData()
  }, [getPostData])

  return (
    <View as={SafeAreaView} style={styles.container}>
      <StatusBar
        barStyle={theme === 'light' ? 'dark-content' : 'light-content'}
        backgroundColor={colors.background}
      />
      {postData ? (
        <FlatList
          data={postData}
          extraData={isLoading}
          refreshing={isLoading}
          onRefresh={() => getPostData()}
          keyExtractor={item => item.id}
          ListHeaderComponent={
            <CategoryPicker selectedCategory={category} onClick={setCategory} addAll />
          }
          ListHeaderComponentStyle={[styles.categoryPicker, { backgroundColor: colors.bgColor }]}
          ListEmptyComponent={
            <Text style={[styles.empty, { color: colors.text }]}>Ups! Not found any post!</Text>
          }
          renderItem={({ item, index }) => (
            <Post
              index={index}
              postId={item.id}
              userId={authState.userInfo.id}
              score={item.score}
              type={item.type}
              title={item.title}
              author={item.author}
              category={item.category}
              text={item.text}
              comments={item.comments}
              created={item.created}
              url={item.url}
              votes={item.votes}
              views={item.views}
              setIsLoaading={setIsLoaading}
              setData={setPostData}
              deleteButton={false}
            />
          )}
        />
      ) : (
        <>
          <CategoryLoader />
          {[1, 2, 3, 4, 5].map(i => (
            <PostLoader key={i} />
          ))}
        </>
      )}
    </View>
  )
}
Example #18
Source File: User.js    From reddit-clone with MIT License 5 votes vote down vote up
User = ({ route }) => {
  const { authState } = React.useContext(AuthContext)
  const { colors } = useTheme()

  const [isLoading, setIsLoaading] = React.useState(false)
  const [userPosts, setuserPosts] = React.useState(null)

  const username = route.params?.username

  const getUserPostDetail = React.useCallback(async () => {
    setIsLoaading(true)
    const { data } = await axios.get(`user/${username || authState.userInfo.username}`)
    setuserPosts(data)
    setIsLoaading(false)
  }, [authState.userInfo.username, username])

  React.useEffect(() => {
    getUserPostDetail()
  }, [getUserPostDetail])

  const deletePost = async (postId, index) => {
    setIsLoaading(true)
    const { status } = await axios.delete(`post/${postId}`)
    if (status === 200) {
      setuserPosts(prevData => {
        prevData.splice(index, 1)
        return prevData
      })
    }
    setIsLoaading(false)
  }

  return (
    <View as={SafeAreaView} style={styles.boxCenter}>
      {userPosts ? (
        <FlatList
          data={userPosts.sort((a, b) => a.created < b.created)}
          extraData={isLoading}
          refreshing={isLoading}
          onRefresh={() => getUserPostDetail()}
          keyExtractor={item => item.id}
          ListEmptyComponent={
            <Text style={[styles.empty, { color: colors.text }]}>Ups! Not found any post!</Text>
          }
          ListHeaderComponent={<HeaderComponent username={username} postCount={userPosts.length} />}
          stickyHeaderIndices={[0]}
          ListHeaderComponentStyle={styles.headerComponentStyle}
          renderItem={({ item, index }) => (
            <Post
              index={index}
              postId={item.id}
              userId={authState.userInfo.id}
              score={item.score}
              type={item.type}
              title={item.title}
              author={item.author}
              category={item.category}
              text={item.text}
              comments={item.comments}
              created={item.created}
              url={item.url}
              votes={item.votes}
              views={item.views}
              setIsLoaading={setIsLoaading}
              setData={setuserPosts}
              deleteButton={true}
              deletePost={() => deletePost(item.id, index)}
            />
          )}
        />
      ) : (
        <>
          {[1, 2, 3, 4, 5].map(i => (
            <PostLoader key={i} />
          ))}
        </>
      )}
    </View>
  )
}
Example #19
Source File: App.js    From Legacy with Mozilla Public License 2.0 4 votes vote down vote up
function App() {
  let [fontsLoaded] = useFonts(Platform.OS == "web" ? {
    // Coiny_400Regular,
  } : {
      Coiny_400Regular,
      Roboto_100Thin,
      Roboto_300Light,
      Roboto_400Regular,
      Roboto_500Medium,
      Roboto_700Bold,
      Roboto_900Black,
    });
  const loadingLogin = useSelector(i => i.loadingLogin || i.version < 0);
  const version = useSelector(i => i.version);
  const ref = React.useRef();
  const dispatch = useDispatch();
  const { t } = useTranslation();

  const { getInitialState } = useLinking(ref, {
    prefixes: ['https://cuppazee.app', 'cuppazee://'],
    config: {
      __primary: {
        screens: {
          ...pageScreens,
          Auth: 'auth',
        },
      }
    },
  });
  var [isReady, setIsReady] = React.useState(false);
  var [initialState, setInitialState] = React.useState();
  var theme = useSelector(i => i.themes[i.theme])

  React.useEffect(() => {
    Promise.race([
      getInitialState(),
      new Promise(resolve =>
        setTimeout(resolve, 150)
      )
    ])
      .catch(e => {
        console.error(e);
      })
      .then(state => {
        if (state !== undefined) {
          setTimeout(() => dispatch(setCurrentRoute(state?.routes?.[0]?.state?.routes?.slice?.()?.reverse?.()?.[0] ?? {})), 100);
          setInitialState(state);
        }

        setIsReady(true);
      });
  }, [getInitialState]);

  function handleStateChange(a) {
    dispatch(setCurrentRoute(a?.routes?.[0]?.state?.routes?.slice?.()?.reverse?.()?.[0] ?? {}))
  }

  if (!theme || !theme.page || !theme.page.bg) {
    return <View style={{ flex: 1, justifyContent: "center", alignItems: "center", backgroundColor: 'white' }}>
      <ActivityIndicator size="large" color="orange" />
    </View>;
  }
  if (!fontsLoaded) {
    return <View style={{ flex: 1, justifyContent: "center", alignItems: "center", backgroundColor: theme.page.bg }}>
      <ActivityIndicator size="large" color={theme.page.fg} />
    </View>;
  }
  if (loadingLogin) {
    return <View style={{ flex: 1, justifyContent: "center", alignItems: "center", backgroundColor: theme.page.bg }}>
      <Text allowFontScaling={false} style={{ ...font("bold"), fontSize: 20, color: theme.page.fg, marginBottom: 20 }}>{t('common:logging_in')}</Text>
      <ActivityIndicator size="large" color={theme.page.fg} />
    </View>;
  }
  if (version != Math.max(...Object.keys(changelogs).map(Number))) {
    var arr = Object.keys(changelogs).map(Number).filter(i => i > version).slice(-10).sort((a, b) => a - b);
    var logs = arr.map(i => [i, changelogs[i]])
    return <SafeAreaView style={{ backgroundColor: theme.navigation.bg, height: "100%" }}>
      <ScrollView
        style={{ flex: 1 }}
        contentContainerStyle={{ padding: 8, justifyContent: "center", alignItems: "center", flexGrow: 1 }}>
        {logs.map(([build, log]) => <View style={{ maxWidth: "100%" }}>
          <View style={{ alignItems: "center" }}>
            <Text allowFontScaling={false} style={{ color: theme.navigation.fg, fontSize: 32, ...font("bold") }}>{t('changelog:build_n', { n: build })}</Text>
          </View>
          {log?.map(i => <View style={{ flexDirection: "row", alignItems: "center", width: 400, maxWidth: "100%" }}>
            {i.image && <Image source={getIcon(i.image)} style={{ height: 48, width: 48 }} />}
            {i.icon && <View style={{ height: 48, width: 48, backgroundColor: theme.page_content.bg, borderRadius: 24, justifyContent: "center", alignItems: "center" }}>
              <MaterialCommunityIcons size={32} color={theme.page_content.fg} name={i.icon} />
            </View>}
            <View style={{ padding: 8, flex: 1 }}>
              <Text allowFontScaling={false} style={{ color: theme.navigation.fg, fontSize: 20, ...font("bold") }}>{i.title}</Text>
              <Text allowFontScaling={false} style={{ color: theme.navigation.fg, fontSize: 16, ...font() }}>{i.description}</Text>
            </View>
          </View>) ?? <Text allowFontScaling={false} style={{ ...font("bold"), fontSize: 20, color: theme.page.fg, marginBottom: 20 }}>{t('changelog:no_changelog')}</Text>}
          {build == Math.max(...Object.keys(changelogs).map(Number)) && <Button mode="contained" style={{ borderWidth: theme.page_content.border ? 2 : 0, borderColor: theme.page_content.border }} color={theme.page_content.bg} onPress={() => {
            dispatch(cuppazeeVersion(Math.max(...Object.keys(changelogs).map(Number))))
          }}>{logs.some(i=>i[1].some(x=>x.privacy))?t('changelog:continue_and_agree'):t('changelog:continue')}</Button>}
        </View>)}
      </ScrollView>
    </SafeAreaView>;
  }
  if (!isReady) {
    return null;
  }
  var navWidth = 400;
  return (
    <NavigationContainer independent={true} onStateChange={handleStateChange} initialState={initialState} ref={ref}>
      <StatusBar translucent={true} backgroundColor={theme.navigation.bg + 'cc'} barStyle="light-content" />
      <View style={{ flex: 1 }}>
        <DrawerNav />
        {/* <View style={{position:"absolute",bottom:-0.5*navWidth,right:-0.5*navWidth,width:navWidth,height:navWidth,borderRadius:navWidth/2,paddingBottom:navWidth/2,paddingRight:navWidth/2,backgroundColor:"white"}}><Text>Hello</Text></View> */}
      </View>
    </NavigationContainer>
  );
}
Example #20
Source File: App.js    From filen-mobile with GNU Affero General Public License v3.0 4 votes vote down vote up
App = memo(() => {
    const [isLoggedIn, setIsLoggedIn] = useMMKVBoolean("isLoggedIn", storage)
    const setDimensions = useStore(useCallback(state => state.setDimensions))
    const [darkMode, setDarkMode] = useMMKVBoolean("darkMode", storage)
    const [setupDone, setSetupDone] = useState(false)
    const [currentScreenName, setCurrentScreenName] = useState("MainScreen")
    const setCurrentRoutes = useStore(useCallback(state => state.setCurrentRoutes))
    const toastBottomOffset = useStore(useCallback(state => state.toastBottomOffset))
    const toastTopOffset = useStore(useCallback(state => state.toastTopOffset))
    const setNetInfo = useStore(useCallback(state => state.setNetInfo))
    const showNavigationAnimation = useStore(useCallback(state => state.showNavigationAnimation))
    const [userId, setUserId] = useMMKVNumber("userId", storage)
    const [cameraUploadEnabled, setCameraUploadEnabled] = useMMKVBoolean("cameraUploadEnabled:" + userId, storage)
    const setBiometricAuthScreenState = useStore(useCallback(state => state.setBiometricAuthScreenState))
    const setCurrentShareItems = useStore(useCallback(state => state.setCurrentShareItems))
    const setAppState = useStore(useCallback(state => state.setAppState))
    const [lang, setLang] = useMMKVString("lang", storage)
    const [nodeJSAlive, setNodeJSAlive] = useState(true)
    const setContentHeight = useStore(useCallback(state => state.setContentHeight))
    const isDeviceReady = useStore(useCallback(state => state.isDeviceReady))
    const [startOnCloudScreen, setStartOnCloudScreen] = useMMKVBoolean("startOnCloudScreen:" + userId, storage)
    const [userSelectedTheme, setUserSelectedTheme] = useMMKVString("userSelectedTheme", storage)
    const [currentDimensions, setCurrentDimensions] = useState({ window: Dimensions.get("window"), screen: Dimensions.get("screen") })

    const handleShare = useCallback(async (items) => {
        if(!items){
            return false
        }

        if(typeof items !== "undefined"){
            if(typeof items.data !== "undefined"){
                if(items.data !== null){
                    if(items.data.length > 0){
                        await new Promise((resolve) => {
                            const wait = BackgroundTimer.setInterval(() => {
                                if(typeof navigationRef !== "undefined"){
                                    const navState = navigationRef.getState()

                                    if(typeof navState !== "undefined"){
                                        if(typeof navState.routes !== "undefined"){
                                            if(navState.routes.filter(route => route.name == "SetupScreen" || route.name == "BiometricAuthScreen" || route.name == "LoginScreen").length == 0){
                                                if(storage.getBoolean("isLoggedIn")){
                                                    BackgroundTimer.clearInterval(wait)
    
                                                    return resolve()
                                                }
                                            }
                                        }
                                    }
                                }
                            }, 250)
                        })

                        let containsValidItems = true

                        if(Platform.OS == "android"){
                            if(Array.isArray(items.data)){
                                for(let i = 0; i < items.data.length; i++){
                                    if(items.data[i].indexOf("file://") == -1 && items.data[i].indexOf("content://") == -1){
                                        containsValidItems = false
                                    }
                                }
                            }
                            else{
                                if(items.data.indexOf("file://") == -1 && items.data.indexOf("content://") == -1){
                                    containsValidItems = false
                                }
                            }
                        }
                        else{
                            for(let i = 0; i < items.data.length; i++){
                                if(items.data[i].data.indexOf("file://") == -1 && items.data[i].data.indexOf("content://") == -1){
                                    containsValidItems = false
                                }
                            }
                        }

                        if(containsValidItems){
                            setCurrentShareItems(items)
                            showToast({ type: "upload" })
                        }
                        else{
                            showToast({ message: i18n(lang, "shareMenuInvalidType") })
                        }
                    }
                }
            }
        }
    })

    const initBackgroundFetch = useCallback(() => {
        BackgroundFetch.configure({
            minimumFetchInterval: 15,
            requiredNetworkType: BackgroundFetch.NETWORK_TYPE_ANY,
            stopOnTerminate: false,
            startOnBoot: true,
            enableHeadless: false
        }, (taskId) => {
            console.log("[" + Platform.OS + "] BG fetch running:", taskId)

            const waitForInit = (callback) => {
                const timeout = (+new Date() + 15000)

                const wait = BackgroundTimer.setInterval(() => {
                    if(timeout > (+new Date())){
                        if(isLoggedIn && cameraUploadEnabled && setupDone && isDeviceReady){
                            BackgroundTimer.clearInterval(wait)

                            return callback(false)
                        }
                    }
                    else{
                        BackgroundTimer.clearInterval(wait)

                        return callback(true)
                    }
                }, 10)
            }

            waitForInit((timedOut) => {
                if(timedOut){
                    console.log("[" + Platform.OS + "] BG fetch timed out:", taskId)

                    BackgroundFetch.finish(taskId)
                }
                else{
                    runCameraUpload({
                        runOnce: true,
                        maxQueue: 1,
                        callback: () => {
                            console.log("[" + Platform.OS + "] BG fetch done:", taskId)
    
                            BackgroundFetch.finish(taskId)
                        }
                    })
                }
            })
        }, (taskId) => {
            console.log("[" + Platform.OS + "] BG fetch timeout:", taskId)

            BackgroundFetch.finish(taskId)
        }).then((status) => {
            console.log("[" + Platform.OS + "] BG fetch init status:", status)
        }).catch((err) => {
            console.log("[" + Platform.OS + "] BG fetch init error:", err)
        })
    })

    const setAppearance = useCallback(() => {
        BackgroundTimer.setTimeout(() => {
            if(typeof userSelectedTheme == "string" && userSelectedTheme.length > 1){
                if(userSelectedTheme == "dark"){
                    setDarkMode(true)
                    setStatusBarStyle(true)
                }
                else{
                    setDarkMode(false)
                    setStatusBarStyle(false)
                }
            }
            else{
                if(Appearance.getColorScheme() == "dark"){
                    setDarkMode(true)
                    setStatusBarStyle(true)
                }
                else{
                    setDarkMode(false)
                    setStatusBarStyle(false)
                }
            }
        }, 1000) // We use a timeout due to the RN appearance event listener firing both "dark" and "light" on app resume which causes the screen to flash for a second
    })

    useEffect(() => {
        if(isLoggedIn && cameraUploadEnabled && setupDone){
            runCameraUpload({
                maxQueue: 10,
                runOnce: false,
                callback: undefined
            })
        }
    }, [isLoggedIn, cameraUploadEnabled, setupDone])

    useEffect(() => {
        initBackgroundFetch()

        //global.nodeThread.pingPong(() => {
        //    setNodeJSAlive(false)
        //})

        NetInfo.fetch().then((state) => {
            setNetInfo(state)
        }).catch((err) => {
            console.log(err)
        })

        //BackgroundTimer.start()

        const appStateListener = AppState.addEventListener("change", (nextAppState) => {
            setAppState(nextAppState)

            if(nextAppState == "background"){
                if(Math.floor(+new Date()) > storage.getNumber("biometricPinAuthTimeout:" + userId) && storage.getBoolean("biometricPinAuth:" + userId)){
                    setBiometricAuthScreenState("auth")

                    storage.set("biometricPinAuthTimeout:" + userId, (Math.floor(+new Date()) + 500000))
                    
                    navigationRef.current.dispatch(StackActions.push("BiometricAuthScreen"))
                }
            }

            if(nextAppState == "active"){
                checkAppVersion({ navigation: navigationRef })
            }
        })

        const netInfoListener = NetInfo.addEventListener((state) => {
            setNetInfo(state)
        })

        const dimensionsListener = Dimensions.addEventListener("change", ({ window, screen }) => {
            setDimensions({ window, screen })
            setCurrentDimensions({ window, screen })
        })

        const navigationRefListener = navigationRef.addListener("state", (event) => {
            if(typeof event.data !== "undefined"){
                if(typeof event.data.state !== "undefined"){
                    if(typeof event.data.state.routes !== "undefined"){
                        //console.log("Current Screen:", event.data.state.routes[event.data.state.routes.length - 1].name, event.data.state.routes[event.data.state.routes.length - 1].params)

                        setCurrentScreenName(event.data.state.routes[event.data.state.routes.length - 1].name)
                        setCurrentRoutes(event.data.state.routes)
                    }
                }
            }
        })

        ShareMenu.getInitialShare(handleShare)

        const shareMenuListener = ShareMenu.addNewShareListener(handleShare)

        setAppearance()

        const appearanceListener = Appearance.addChangeListener(() => {
            setAppearance()
        })

        if(isLoggedIn && !setupDone){
            setup({ navigation: navigationRef }).then(() => {
                setSetupDone(true)

                if(storage.getBoolean("biometricPinAuth:" + userId)){
                    setBiometricAuthScreenState("auth")

                    storage.set("biometricPinAuthTimeout:" + userId, (Math.floor(+new Date()) + 500000))
                    
                    navigationRef.current.dispatch(StackActions.push("BiometricAuthScreen"))
                }
                else{
                    navigationRef.current.dispatch(CommonActions.reset({
                        index: 0,
                        routes: [
                            {
                                name: "MainScreen",
                                params: {
                                    parent: startOnCloudScreen ? (storage.getBoolean("defaultDriveOnly:" + userId) ? storage.getString("defaultDriveUUID:" + userId) : "base") : "recents"
                                }
                            }
                        ]
                    }))
                }
            }).catch((err) => {
                console.log(err)
    
                if(typeof storage.getString("masterKeys") == "string" && typeof storage.getString("apiKey") == "string" && typeof storage.getString("privateKey") == "string" && typeof storage.getString("publicKey") == "string" && typeof storage.getNumber("userId") == "number"){
                    if(storage.getString("masterKeys").length > 16 && storage.getString("apiKey").length > 16 && storage.getString("privateKey").length > 16 && storage.getString("publicKey").length > 16 && storage.getNumber("userId") !== 0){
                        setSetupDone(true)

                        if(storage.getBoolean("biometricPinAuth:" + userId)){
                            setBiometricAuthScreenState("auth")

                            storage.set("biometricPinAuthTimeout:" + userId, (Math.floor(+new Date()) + 500000))
                            
                            navigationRef.current.dispatch(StackActions.push("BiometricAuthScreen"))
                        }
                        else{
                            navigationRef.current.dispatch(CommonActions.reset({
                                index: 0,
                                routes: [
                                    {
                                        name: "MainScreen",
                                        params: {
                                            parent: startOnCloudScreen ? (storage.getBoolean("defaultDriveOnly:" + userId) ? storage.getString("defaultDriveUUID:" + userId) : "base") : "recents"
                                        }
                                    }
                                ]
                            }))
                        }
                    }
                    else{
                        setSetupDone(false)

                        showToast({ message: i18n(lang, "appSetupNotPossible") })
                    }
                }
                else{
                    setSetupDone(false)

                    showToast({ message: i18n(lang, "appSetupNotPossible") })
                }
            })
        }

        // Reset on app launch
        storage.set("cameraUploadRunning", false)

        return () => {
            dimensionsListener.remove()
            shareMenuListener.remove()
            navigationRef.removeListener(navigationRefListener)
            navigationRefListener()
            appearanceListener.remove()
            netInfoListener()
            appStateListener.remove()
        }
    }, [])

  	return (
        <>
            <NavigationContainer ref={navigationRef}>
                <Fragment>
                    <SafeAreaProvider style={{
                        backgroundColor: darkMode ? "black" : "white",
                    }}>
                        <SafeAreaView mode="padding" style={{
                            backgroundColor: currentScreenName == "ImageViewerScreen" ? "black" : (darkMode ? "black" : "white"),
                            paddingTop: Platform.OS == "android" ? 5 : 5,
                            height: "100%",
                            width: "100%"
                        }}>
                            <View style={{
                                width: currentScreenName == "ImageViewerScreen" ? currentDimensions.screen.width : "100%",
                                height: currentScreenName == "ImageViewerScreen" ? currentDimensions.screen.height : "100%",
                                backgroundColor: darkMode ? "black" : "white"
                            }} onLayout={(e) => setContentHeight(e.nativeEvent.layout.height)}>
                                {
                                    nodeJSAlive ? (
                                        <>
                                            <Stack.Navigator initialRouteName={isLoggedIn ? (setupDone ? "MainScreen" : "SetupScreen") : "LoginScreen"} screenOptions={{
                                                contentStyle: {
                                                    backgroundColor: darkMode ? "black" : "white"
                                                },
                                                headerStyle: {
                                                    backgroundColor: darkMode ? "black" : "white"
                                                },
                                                headerShown: false,
                                                animation: showNavigationAnimation ? "default" : "none"
                                            }}>
                                                <Stack.Screen name="SetupScreen" component={SetupScreen} options={{
                                                    title: "SetupScreen"
                                                }}></Stack.Screen>
                                                <Stack.Screen name="LoginScreen" options={{
                                                    title: "LoginScreen"
                                                }}>{(props) => <LoginScreen {...props} setSetupDone={setSetupDone} />}</Stack.Screen>
                                                <Stack.Screen name="RegisterScreen" component={RegisterScreen} options={{
                                                    title: "RegisterScreen"
                                                }}></Stack.Screen>
                                                <Stack.Screen name="ForgotPasswordScreen" component={ForgotPasswordScreen} options={{
                                                    title: "ForgotPasswordScreen"
                                                }}></Stack.Screen>
                                                <Stack.Screen name="ResendConfirmationScreen" component={ResendConfirmationScreen} options={{
                                                    title: "ResendConfirmationScreen"
                                                }}></Stack.Screen>
                                                <Stack.Screen name="MainScreen" initialParams={{ parent: startOnCloudScreen ? (storage.getBoolean("defaultDriveOnly:" + userId) ? storage.getString("defaultDriveUUID:" + userId) : "base") : "recents" }} component={MainScreen} options={{
                                                    title: "MainScreen"
                                                }}></Stack.Screen>
                                                <Stack.Screen name="SettingsScreen" component={SettingsScreen} options={{
                                                    title: "SettingsScreen"
                                                }}></Stack.Screen>
                                                <Stack.Screen name="TransfersScreen" component={TransfersScreen} options={{
                                                    title: "TransfersScreen"
                                                }}></Stack.Screen>
                                                <Stack.Screen name="CameraUploadScreen" component={CameraUploadScreen} options={{
                                                    title: "CameraUploadScreen"
                                                }}></Stack.Screen>
                                                <Stack.Screen name="BiometricAuthScreen" component={BiometricAuthScreen} options={{
                                                    title: "BiometricAuthScreen"
                                                }}></Stack.Screen>
                                                <Stack.Screen name="LanguageScreen" component={LanguageScreen} options={{
                                                    title: "LanguageScreen"
                                                }}></Stack.Screen>
                                                <Stack.Screen name="SettingsAdvancedScreen" component={SettingsAdvancedScreen} options={{
                                                    title: "SettingsAdvancedScreen"
                                                }}></Stack.Screen>
                                                <Stack.Screen name="SettingsAccountScreen" component={SettingsAccountScreen} options={{
                                                    title: "SettingsAccountScreen"
                                                }}></Stack.Screen>
                                                <Stack.Screen name="EventsScreen" component={EventsScreen} options={{
                                                    title: "EventsScreen"
                                                }}></Stack.Screen>
                                                <Stack.Screen name="EventsInfoScreen" component={EventsInfoScreen} options={{
                                                    title: "EventsInfoScreen"
                                                }}></Stack.Screen>
                                                <Stack.Screen name="GDPRScreen" component={GDPRScreen} options={{
                                                    title: "GDPRScreen"
                                                }}></Stack.Screen>
                                                <Stack.Screen name="InviteScreen" component={InviteScreen} options={{
                                                    title: "InviteScreen"
                                                }}></Stack.Screen>
                                                <Stack.Screen name="TwoFactorScreen" component={TwoFactorScreen} options={{
                                                    title: "TwoFactorScreen"
                                                }}></Stack.Screen>
                                                <Stack.Screen name="ChangeEmailPasswordScreen" component={ChangeEmailPasswordScreen} options={{
                                                    title: "ChangeEmailPasswordScreen"
                                                }}></Stack.Screen>
                                                <Stack.Screen name="TextEditorScreen" component={TextEditorScreen} options={{
                                                    title: "TextEditorScreen"
                                                }}></Stack.Screen>
                                                <Stack.Screen name="UpdateScreen" component={UpdateScreen} options={{
                                                    title: "UpdateScreen"
                                                }}></Stack.Screen>
                                                <Stack.Screen name="ImageViewerScreen" component={ImageViewerScreen} options={{
                                                    title: "ImageViewerScreen",
                                                    presentation: "fullScreenModal"
                                                }}></Stack.Screen>
                                            </Stack.Navigator>
                                            <>
                                                {
                                                    setupDone && isLoggedIn && ["MainScreen", "SettingsScreen", "TransfersScreen", "CameraUploadScreen", "EventsScreen", "EventsInfoScreen", "SettingsAdvancedScreen", "SettingsAccountScreen", "LanguageScreen", "GDPRScreen", "InviteScreen", "TwoFactorScreen", "ChangeEmailPasswordScreen"].includes(currentScreenName) && (
                                                        <View style={{
                                                            position: "relative",
                                                            width: "100%",
                                                            bottom: 0,
                                                            height: 50
                                                        }}>
                                                            <BottomBar navigation={navigationRef} currentScreenName={currentScreenName} />
                                                        </View>
                                                    )
                                                }
                                            </>
                                        </>
                                    ) : (
                                        <View style={{
                                            width: "100%",
                                            height: "100%",
                                            justifyContent: "center",
                                            alignItems: "center"
                                        }}>
                                            <Ionicon name="information-circle-outline" size={70} color={darkMode ? "white" : "black"} />
                                            <Text style={{
                                                color: darkMode ? "white" : "black",
                                                marginTop: 5,
                                                width: "70%",
                                                textAlign: "center"
                                            }}>
                                                {i18n(lang, "nodeJSProcessDied")}
                                            </Text>
                                        </View>
                                    )
                                }
                                {
                                    nodeJSAlive && (
                                        <>
                                            <TransfersIndicator navigation={navigationRef} />
                                            <TopBarActionSheet navigation={navigationRef} />
                                            <BottomBarAddActionSheet navigation={navigationRef} />
                                            <ItemActionSheet navigation={navigationRef} />
                                            <FolderColorActionSheet navigation={navigationRef} />
                                            <PublicLinkActionSheet navigation={navigationRef} />
                                            <ShareActionSheet navigation={navigationRef} />
                                            <FileVersionsActionSheet navigation={navigationRef} />
                                            <ProfilePictureActionSheet navigation={navigationRef} />
                                            <SortByActionSheet navigation={navigationRef} />
                                        </>
                                    )
                                }
                            </View>
                        </SafeAreaView>
                    </SafeAreaProvider>
                    {
                        nodeJSAlive && (
                            <>
                                <Disable2FATwoFactorDialog navigation={navigationRef} />
                                <DeleteAccountTwoFactorDialog navigation={navigationRef} />
                                <RedeemCodeDialog navigation={navigationRef} />
                                <ConfirmStopSharingDialog navigation={navigationRef} />
                                <ConfirmRemoveFromSharedInDialog navigation={navigationRef} />
                                <ConfirmPermanentDeleteDialog navigation={navigationRef} />
                                <RenameDialog navigation={navigationRef} />
                                <CreateFolderDialog navigation={navigationRef} />
                                <CreateTextFileDialog navigation={navigationRef} />
                                <BulkShareDialog navigation={navigationRef} />
                                <FullscreenLoadingModal navigation={navigationRef} />
                            </>
                        )
                    }
                </Fragment>
            </NavigationContainer>
            <Toast
                ref={(ref) => global.toast = ref}
                offsetBottom={toastBottomOffset}
                offsetTop={toastTopOffset}
                pointerEvents="box-none"
                style={{
                    zIndex: 99999
                }}
            />
        </>
    )
})
Example #21
Source File: Confirm.js    From actual with MIT License 4 votes vote down vote up
function Confirm({ route, navigation, getUserData, loginUser, createBudget }) {
  let [code, setCode] = useState('');
  let [loading, setLoading] = useState(false);
  let [error, setError] = useState(null);

  let { signingUp } = route.params || {};

  async function onConfirm() {
    let { email } = route.params || {};
    setLoading(true);

    let { confirmed, error, userId, key, validSubscription } = await send(
      'subscribe-confirm',
      { email, code }
    );

    if (error) {
      setLoading(false);
      setError(getErrorMessage(error));
    } else if (!confirmed) {
      setLoading(false);
      setError(getErrorMessage('not-confirmed'));
    } else if (!validSubscription) {
      if (Platform.OS === 'ios') {
        // Eagerly load in the offerings (otherwise the subscribe button
        // shows a loading state which is weird)
        await setupPurchases({ id: userId, email: email });
        await getOfferings();

        setLoading(false);
        navigation.navigate('Subscribe', { email, userId, key });
      } else {
        // This is a "half-created" account, right now on Android we
        // don't fix it here, we just tell the user. This is super
        // rare - only happens if a user on iOS creates an account but
        // doesn't subscribe, and then tries to log in on Android with
        // that account
        alert(
          'An error occurred loading your account. Please contact [email protected] for support'
        );
        setLoading(false);
      }
    } else {
      setLoading(false);

      // This will load the user in the backend and rerender the app
      // in the logged in state
      loginUser(userId, key);

      if (global.SentryClient) {
        global.SentryClient.setUser({
          id: userId,
          ip_address: '{{auto}}'
        });
      }
    }
  }

  let textStyle = [
    styles.text,
    { fontSize: 17, lineHeight: 25, color: 'white' }
  ];

  return (
    <SafeAreaView style={{ flex: 1 }}>
      <KeyboardAvoidingView>
        <TransitionView navigation={navigation}>
          <Header
            navigation={navigation}
            buttons={['back', 'demo']}
            loadDemoBudget={() => createBudget({ demoMode: true })}
          />

          <Stack justify="center" style={{ flex: 1, padding: 20 }} spacing={5}>
            <View>
              {signingUp ? (
                <Text style={textStyle}>
                  Enter the code you got in your email to activate your account:
                </Text>
              ) : (
                <Text style={textStyle}>
                  Enter the code you got in your email:
                </Text>
              )}
            </View>

            <SingleInput
              title="Code"
              value={code}
              loading={loading}
              error={error}
              inputProps={{ keyboardType: 'numeric' }}
              onChange={setCode}
              onSubmit={onConfirm}
            />
          </Stack>
        </TransitionView>
      </KeyboardAvoidingView>
    </SafeAreaView>
  );
}
Example #22
Source File: Intro.js    From actual with MIT License 4 votes vote down vote up
render() {
    let { navigation, createBudget } = this.props;
    let { selectedFeature } = this.state;

    //let textStyle = [styles.text, { color: 'white' }];

    return (
      <SafeAreaView style={{ flex: 1 }}>
        <TransitionView navigation={navigation}>
          <View
            style={{ justifyContent: 'center', alignItems: 'center', flex: 1 }}
          >
            <ScalableImage
              source={Icon}
              width={35}
              style={{ marginBottom: 20, marginTop: 30 }}
            />

            <View style={{ height: 240 }}>
              <ScrollView
                ref={el => (this.scrollView = el)}
                pagingEnabled={true}
                horizontal={true}
                showsHorizontalScrollIndicator={false}
                onScrollBeginDrag={this.onScrollBegin}
                onScrollEndDrag={this.onScrollEnd}
                onMomentumScrollEnd={this.onMomentumScrollEnd}
              >
                <View
                  style={{
                    flexDirection: 'row',
                    alignItems: 'center',
                    flex: 0
                  }}
                >
                  <Feature
                    title="Welcome to Actual"
                    subtitle={
                      <Text>
                        Actual is a privacy-focused app that lets you track your
                        finances without all the fuss. Create your own budgeting
                        workflows quickly and discover your spending habits.{' '}
                        <ExternalLink href="https://actualbudget.com/">
                          Learn more
                        </ExternalLink>
                      </Text>
                    }
                  />
                  <Feature
                    title="Powerful budgeting made simple"
                    subtitle="Based on tried and true methods, our budgeting system is based off of your real income instead of made up numbers."
                  />
                  <Feature
                    title="The fastest way to manage transactions"
                    subtitle="Breeze through your transactions and update them easily with a streamlined, minimal interface."
                  />
                  <Feature
                    title="A privacy-focused approach"
                    subtitle="All of your data exists locally and is always available. We only upload your data to our servers when syncing across devices, and we encrypt it so even we can't read it."
                  />
                </View>
              </ScrollView>
              <View style={{ alignItems: 'center' }}>
                <View
                  style={{
                    flexDirection: 'row',
                    justifyContent: 'space-between',
                    width: 45
                  }}
                >
                  <SlideshowIndicator selected={selectedFeature === 0} />
                  <SlideshowIndicator selected={selectedFeature === 1} />
                  <SlideshowIndicator selected={selectedFeature === 2} />
                  <SlideshowIndicator selected={selectedFeature === 3} />
                </View>
              </View>
            </View>
          </View>

          <View
            style={{
              marginHorizontal: 50,
              marginBottom: 15,
              justifyContent: 'flex-end'
            }}
          >
            <Button
              primary
              style={{ marginBottom: 10, backgroundColor: 'white' }}
              contentStyle={{ borderWidth: 0 }}
              textStyle={{ fontSize: 17, color: colors.n1 }}
              onPress={() => {
                navigation.navigate('SubscribeEmail');
              }}
            >
              Get started
            </Button>
            <Button
              style={{
                marginBottom: 10,
                backgroundColor: 'rgba(180, 180, 180, .15)'
              }}
              contentStyle={{ borderWidth: 0 }}
              textStyle={{ fontSize: 17, color: 'white' }}
              onPress={() => {
                navigation.navigate('Login');
              }}
            >
              Log in
            </Button>
          </View>
          <Button
            bare
            textStyle={{ fontWeight: 'bold', fontSize: 15, color: 'white' }}
            style={{ padding: 10, alignSelf: 'center' }}
            onPress={() => createBudget({ demoMode: true })}
          >
            Try demo
          </Button>
        </TransitionView>
      </SafeAreaView>
    );
  }
Example #23
Source File: SubscribeEmail.js    From actual with MIT License 4 votes vote down vote up
export function SubscribeEmail({ navigation, createBudget }) {
  let [email, setEmail] = useState('');
  let [error, setError] = useState(null);
  let [loading, setLoading] = useState(false);

  async function eagerlyLoadOfferings(userId, email) {
    await iap.setupPurchases({ id: userId, email });
    iap.getOfferings();
  }

  async function onSignup() {
    setLoading(true);
    setError(null);
    let { error, userId, key } = await send('subscribe-subscribe', {
      email,
      useStripe: Platform.OS !== 'ios'
    });

    if (error) {
      setLoading(false);
      setError(getSubscribeError(error));
    } else {
      if (Platform.OS === 'ios') {
        // Don't block on this, but start loading the available offerings
        // now so when they see the subscribe screen later they don't see
        // a loading screen
        eagerlyLoadOfferings(userId, email);
      }

      let { error } = await send('subscribe-send-email-code', { email });

      if (error) {
        setError('Something went wrong while activating your account');
        return;
      }

      setLoading(false);
      navigation.navigate('Confirm', { email, signingUp: true });
    }
  }

  let textStyle = [
    styles.text,
    { fontSize: 17, lineHeight: 25, color: 'white' }
  ];

  return (
    <SafeAreaView style={{ flex: 1 }}>
      <KeyboardAvoidingView>
        <TransitionView navigation={navigation}>
          {/* <StatusBar barStyle="light-content" /> */}

          <Header
            navigation={navigation}
            loadDemoBudget={() => createBudget({ demoMode: true })}
          />
          <Stack justify="center" style={{ flex: 1, padding: 20 }} spacing={5}>
            <View>
              <Text style={[textStyle, { maxWidth: 500 }]}>
                <Text style={{ fontWeight: '700' }}>Create an account.</Text>{' '}
                Sign up to sync your data across all devices. By default all
                your data is local. In the future we will also provide bank
                syncing.
              </Text>
            </View>

            <SingleInput
              title="Email"
              value={email}
              loading={loading}
              error={error}
              inputProps={{
                keyboardType: 'email-address',
                placeholder: '[email protected]'
              }}
              onChange={setEmail}
              onSubmit={onSignup}
            />
          </Stack>
        </TransitionView>
      </KeyboardAvoidingView>
    </SafeAreaView>
  );
}
Example #24
Source File: Modal.js    From actual with MIT License 4 votes vote down vote up
render() {
    let {
      title,
      style,
      children,
      animate,
      backgroundColor,
      rightButton,
      allowScrolling = true,
      edges = ['top', 'bottom']
    } = this.props;
    animate = false;

    return (
      <SafeAreaView
        edges={edges}
        style={{ flex: 1, backgroundColor: 'transparent' }}
      >
        <KeyboardAvoidingView>
          <FocusAwareStatusBar barStyle="light-content" />
          <Animated.View
            style={[
              { flex: 1 },
              animate && {
                opacity: this.opening,
                transform: [
                  {
                    translateY: this.opening.interpolate({
                      inputRange: [0, 1],
                      outputRange: [10, 0]
                    })
                  }
                ]
              }
            ]}
          >
            <View
              style={{
                flex: 1,
                shadowColor: colors.n3,
                shadowOffset: { width: 0, height: 0 },
                shadowRadius: 4,
                shadowOpacity: 1,
                elevation: 2
              }}
            >
              <View
                style={[
                  {
                    margin: 7,
                    borderRadius: 4,
                    overflow: 'hidden',
                    backgroundColor: backgroundColor || colors.n11,
                    flex: 1
                  },
                  style
                ]}
                onLayout={this.onLayout}
                ref={el => (this.modal = el)}
              >
                <View
                  style={{
                    alignSelf: 'stretch',
                    alignItems: 'center',
                    paddingVertical: 15,
                    paddingLeft: 15,
                    backgroundColor: backgroundColor || colors.n11,
                    borderColor: colors.n10,
                    borderBottomWidth: 1
                  }}
                >
                  <Text
                    style={{
                      color: colors.n1,
                      fontSize: 20,
                      fontWeight: '700'
                    }}
                    numberOfLines={1}
                  >
                    {title}
                  </Text>
                  {rightButton && (
                    <View
                      style={{
                        position: 'absolute',
                        right: 0,
                        top: 0,
                        bottom: 0,
                        justifyContent: 'center'
                      }}
                    >
                      {rightButton}
                    </View>
                  )}
                </View>
                {allowScrolling ? (
                  <ScrollView style={{ flex: 1 }}>{children}</ScrollView>
                ) : (
                  children
                )}
              </View>
            </View>
          </Animated.View>
        </KeyboardAvoidingView>
      </SafeAreaView>
    );
  }
Example #25
Source File: CharacterScreen.js    From hero with MIT License 4 votes vote down vote up
CharacterScreen = ({ route, navigation }) => {
  const {
    hero,
    image,
    publisher,
    comicPicture,
    summary,
    firstIssue,
    firstIssueURL,
  } = route.params;

  let publisherLogo = null;
  let logoShape = null;
  //   let firstIssueID = null;

  if (publisher === "Marvel Comics" || publisher === "Marvel") {
    publisherLogo = require(`../assets/images/Marvel-Logo.jpg`);
    logoShape = styles.publisherLogoRectangle;
  } else if (publisher === "DC Comics") {
    publisherLogo = require(`../assets/images/DC-Logo.png`);
    logoShape = styles.publisherLogoSquare;
  }

  const activeLightboxProps = {
    resizeMode: "contain",
    marginHorizontal: 20,
    flex: 1,
    width: null,
  };

  //   if (comicPicture) {
  //     comicPictureURL = require(comicPicture);
  //   }

  function searchComic(firstComic) {
    fetch(
      `https://comicvine.gamespot.com/api/characters/?api_key=${apiComic.key}&sort=deck:desc&filter=name:${hero.name}&format=json`
    )
      .then((res) => {
        if (res.status === 404) {
          throw new Error("I didn't find this hero. Please try again!");
        } else {
          return res.json();
        }
      })

      .then((result) => {
        console.log("====================================");
        console.log("NEW SEARCH");
        console.log("====================================");
        console.log(result.results);
      });
  }

  function searchFirstComic() {
    fetch(
      `https://comicvine.gamespot.com/api/issue/4000-${firstIssue.id}/?api_key=${apiComic.key}&format=json`
    )
      .then((res) => {
        if (res.status === 404) {
          throw new Error("I didn't find this hero. Please try again!");
        } else {
          return res.json();
        }
      })

      .then((result) => {
        console.log("FIRST ISSUE DETAILS");
        console.log(result.results.image.original_url);
        // comicPicture = require(result.results[0].image.original_url);
        // summary = result.results[0].deck;
      });
  }

  //   useEffect(() => {
  //     // console.log(publisherVine);
  //     // searchComic(hero.biography["first-appearance"]);
  //     //   searchFirstComic();
  //   }, []);

  return (
    <View style={styles.appContainer}>
      <StatusBar
        translucent
        backgroundColor="transparent"
        barStyle="dark-content"
      />
      <SafeAreaView
        style={{ flex: 1, backgroundColor: "transparent" }}
        forceInset={{ top: "always" }}
      >
        <Header title={""} backbutton={true} navigation={navigation} />
        <Image source={image} style={styles.heroImage} />
        <LinearGradient
          colors={["#ffffff00", COLORS.beige]}
          style={styles.bottomFade}
          locations={[0.3, 1]}
        />
        <View style={styles.heroInfoContainer}>
          <View style={styles.heroTitleContainer}>
            <Text style={styles.heroTitle}>{hero.name}</Text>
            <View style={styles.heroHeader}>
              <Text style={{ ...styles.h4, marginLeft: 3, fontSize: 16 }}>
                {hero.biography["full-name"]}
              </Text>
              <Image source={publisherLogo} style={logoShape} />
            </View>
          </View>
          <Divider
            orientation="horizontal"
            width={3}
            style={{ ...styles.divider, marginBottom: 0 }}
            color={COLORS.navy}
          />
          <ScrollView
            style={{ height: 340 }}
            contentContainerStyle={{
              width: "100%",
              paddingBottom: 40,
              marginTop: 10,
            }}
            showsVerticalScrollIndicator={false}
          >
            <Text
              style={{
                ...styles.p,
                fontSize: 12,
                marginBottom: 20,
                lineHeight: 18,
              }}
            >
              {summary}
            </Text>
            {/********** STATS DETAILS ***************/}
            <View
              style={{
                flexDirection: "row",
                flexWrap: "wrap",
                justifyContent: "space-around",
                alignItems: "center",
                // backgroundColor: COLORS.grey,
                borderRadius: 20,
                // padding: 10,
                marginBottom: 10,
              }}
            >
              <View
                style={{
                  alignItems: "center",
                  justifyContent: "center",
                  padding: 5,
                }}
              >
                <AnimatedCircularProgress
                  size={60}
                  width={10}
                  duration={2000}
                  backgroundWidth={8}
                  rotation={-124}
                  arcSweepAngle={250}
                  fill={Number(hero.powerstats.intelligence)}
                  tintColor={COLORS.red}
                  tintColorSecondary={COLORS.green}
                  // onAnimationComplete={() => console.log("onAnimationComplete")}
                  backgroundColor={COLORS.navy}
                  padding={0}
                  lineCap={"round"}
                  // renderCap={({ center }) => (
                  //   <Circle cx={center.x} cy={center.y} r="10" fill="blue" />
                  // )}
                >
                  {(fill) => (
                    <Text
                      style={{
                        ...styles.p,
                        fontFamily: "Flame-Regular",
                        left: 1,
                      }}
                    >
                      {Math.floor(fill)}
                    </Text>
                  )}
                </AnimatedCircularProgress>
                <Text
                  style={{
                    ...styles.p,
                    fontFamily: "Flame-Regular",
                    fontSize: 10,
                    marginTop: -10,
                  }}
                >
                  Intelligence
                </Text>
              </View>
              <View
                style={{
                  alignItems: "center",
                  justifyContent: "center",
                  padding: 5,
                }}
              >
                <AnimatedCircularProgress
                  size={60}
                  width={10}
                  duration={2000}
                  backgroundWidth={8}
                  rotation={-124}
                  arcSweepAngle={250}
                  fill={Number(hero.powerstats.strength)}
                  tintColor={COLORS.red}
                  tintColorSecondary={COLORS.green}
                  // onAnimationComplete={() => console.log("onAnimationComplete")}
                  backgroundColor={COLORS.navy}
                  padding={0}
                  lineCap={"round"}
                  // renderCap={({ center }) => (
                  //   <Circle cx={center.x} cy={center.y} r="10" fill="blue" />
                  // )}
                >
                  {(fill) => (
                    <Text
                      style={{
                        ...styles.p,
                        fontFamily: "Flame-Regular",
                        left: 1,
                      }}
                    >
                      {Math.floor(fill)}
                    </Text>
                  )}
                </AnimatedCircularProgress>
                <Text
                  style={{
                    ...styles.p,
                    fontFamily: "Flame-Regular",
                    fontSize: 10,
                    marginTop: -10,
                  }}
                >
                  Strength
                </Text>
              </View>
              <View
                style={{
                  alignItems: "center",
                  justifyContent: "center",
                  padding: 5,
                }}
              >
                <AnimatedCircularProgress
                  size={60}
                  width={10}
                  duration={2000}
                  backgroundWidth={8}
                  rotation={-124}
                  arcSweepAngle={250}
                  fill={Number(hero.powerstats.speed)}
                  tintColor={COLORS.red}
                  tintColorSecondary={COLORS.green}
                  // onAnimationComplete={() => console.log("onAnimationComplete")}
                  backgroundColor={COLORS.navy}
                  padding={0}
                  lineCap={"round"}
                  // renderCap={({ center }) => (
                  //   <Circle cx={center.x} cy={center.y} r="10" fill="blue" />
                  // )}
                >
                  {(fill) => (
                    <Text
                      style={{
                        ...styles.p,
                        fontFamily: "Flame-Regular",
                        left: 1,
                      }}
                    >
                      {Math.floor(fill)}
                    </Text>
                  )}
                </AnimatedCircularProgress>
                <Text
                  style={{
                    ...styles.p,
                    fontFamily: "Flame-Regular",
                    fontSize: 10,
                    marginTop: -10,
                  }}
                >
                  Speed
                </Text>
              </View>
              <View
                style={{
                  alignItems: "center",
                  justifyContent: "center",
                  padding: 5,
                }}
              >
                <AnimatedCircularProgress
                  size={60}
                  width={10}
                  duration={2000}
                  backgroundWidth={8}
                  rotation={-124}
                  arcSweepAngle={250}
                  fill={Number(hero.powerstats.durability)}
                  tintColor={COLORS.red}
                  tintColorSecondary={COLORS.green}
                  // onAnimationComplete={() => console.log("onAnimationComplete")}
                  backgroundColor={COLORS.navy}
                  padding={0}
                  lineCap={"round"}
                  // renderCap={({ center }) => (
                  //   <Circle cx={center.x} cy={center.y} r="10" fill="blue" />
                  // )}
                >
                  {(fill) => (
                    <Text
                      style={{
                        ...styles.p,
                        fontFamily: "Flame-Regular",
                        left: 1,
                      }}
                    >
                      {Math.floor(fill)}
                    </Text>
                  )}
                </AnimatedCircularProgress>
                <Text
                  style={{
                    ...styles.p,
                    fontFamily: "Flame-Regular",
                    fontSize: 10,
                    marginTop: -10,
                  }}
                >
                  Durability
                </Text>
              </View>
              <View
                style={{
                  alignItems: "center",
                  justifyContent: "center",
                  padding: 5,
                }}
              >
                <AnimatedCircularProgress
                  size={60}
                  width={10}
                  duration={2000}
                  backgroundWidth={8}
                  rotation={-124}
                  arcSweepAngle={250}
                  fill={Number(hero.powerstats.power)}
                  tintColor={COLORS.red}
                  tintColorSecondary={COLORS.green}
                  // onAnimationComplete={() => console.log("onAnimationComplete")}
                  backgroundColor={COLORS.navy}
                  padding={0}
                  lineCap={"round"}
                  // renderCap={({ center }) => (
                  //   <Circle cx={center.x} cy={center.y} r="10" fill="blue" />
                  // )}
                >
                  {(fill) => (
                    <Text
                      style={{
                        ...styles.p,
                        fontFamily: "Flame-Regular",
                        left: 1,
                      }}
                    >
                      {Math.floor(fill)}
                    </Text>
                  )}
                </AnimatedCircularProgress>
                <Text
                  style={{
                    ...styles.p,
                    fontFamily: "Flame-Regular",
                    fontSize: 10,
                    marginTop: -10,
                  }}
                >
                  Power
                </Text>
              </View>
              <View
                style={{
                  alignItems: "center",
                  justifyContent: "center",
                  padding: 5,
                }}
              >
                <AnimatedCircularProgress
                  size={60}
                  width={10}
                  duration={2000}
                  backgroundWidth={8}
                  rotation={-124}
                  arcSweepAngle={250}
                  fill={Number(hero.powerstats.combat)}
                  tintColor={COLORS.red}
                  tintColorSecondary={COLORS.green}
                  // onAnimationComplete={() => console.log("onAnimationComplete")}
                  backgroundColor={COLORS.navy}
                  padding={0}
                  lineCap={"round"}
                  // renderCap={({ center }) => (
                  //   <Circle cx={center.x} cy={center.y} r="10" fill="blue" />
                  // )}
                >
                  {(fill) => (
                    <Text
                      style={{
                        ...styles.p,
                        fontFamily: "Flame-Regular",
                        left: 1,
                      }}
                    >
                      {Math.floor(fill)}
                    </Text>
                  )}
                </AnimatedCircularProgress>
                <Text
                  style={{
                    ...styles.p,
                    fontFamily: "Flame-Regular",
                    fontSize: 10,
                    marginTop: -10,
                  }}
                >
                  Combat
                </Text>
              </View>
            </View>
            {/********** BIOGRAPHY DETAILS ***************/}
            <View style={styles.heroDetailsContainer}>
              <Text style={{ ...styles.h2 }}>Biography</Text>
              <Divider
                orientation="horizontal"
                width={2}
                inset={true}
                style={styles.divider}
                color={COLORS.navy}
              />
              {Object.entries(hero.biography).map(([key, value, index]) => {
                // console.log(`${key}: ${value}`);

                let str = value.toString();

                if (
                  key != "full-name" &&
                  key != "place-of-birth" &&
                  key != "first-appearance" &&
                  key != "alter-egos" &&
                  "No alter egos found."
                ) {
                  str = str.replace(/,|;\s*(?![^()]*\))/g, "\n\u2022 ");
                }

                return (
                  <View
                    key={index}
                    style={{
                      flexDirection:
                        key == "aliases" || key == "alter-egos"
                          ? "column"
                          : "row",
                      justifyContent: "space-between",
                      alignItems: "flex-start",
                      flexWrap: "wrap",
                      marginBottom: 5,
                    }}
                  >
                    <Text style={styles.h4}>{key}:</Text>

                    {str.split(`/,[s]*/g, ", "`).map((value) => (
                      <Text
                        style={{ ...styles.p, textTransform: "capitalize" }}
                      >
                        {key == "alter-egos" || key == "aliases"
                          ? "\u2022 " + value
                          : value && value == "-"
                          ? "unknown"
                          : value}
                      </Text>
                    ))}
                  </View>
                );
              })}
            </View>
            {/********** COMIC PICTURE ***************/}
            <View style={styles.comicPictureContainer}>
              {firstIssueURL ? (
                <Lightbox
                  // renderHeader={() => {
                  //   return (
                  //     <View
                  //       style={{
                  //         justifyContent: "center",
                  //         alignItems: "flex-start",
                  //         paddingHorizontal: 15,
                  //         top: 70,
                  //       }}
                  //     >
                  //       <Text
                  //         style={{ ...styles.heroTitle, color: COLORS.beige }}
                  //       >
                  //         First Issue
                  //       </Text>
                  //     </View>
                  //   );
                  // }}
                  activeProps={activeLightboxProps}
                >
                  <Image
                    source={{
                      uri: firstIssueURL,
                    }}
                    style={styles.comicPicture}
                  />
                </Lightbox>
              ) : (
                <Text style={styles.h4}>NO PICTURE</Text>
              )}
            </View>
            {/********** APPEARANCE DETAILS ***************/}
            <View style={styles.heroDetailsContainer}>
              <Text style={{ ...styles.h2 }}>Appearence</Text>
              <Divider
                orientation="horizontal"
                width={2}
                inset={true}
                style={styles.divider}
                color={COLORS.navy}
              />
              {Object.entries(hero.appearance).map(([key, value, index]) => {
                // console.log(`${key}: ${value}`);
                const str = value.toString();
                return (
                  <View
                    key={index}
                    style={{
                      flexDirection: "row",
                      justifyContent: "space-between",
                      alignItems: "stretch",
                      flexWrap: "wrap",
                    }}
                  >
                    <Text style={styles.h4}>{key}:</Text>
                    <Text style={{ ...styles.p, marginTop: -2 }}>
                      {str
                        .split(`/,[s]*/g, ", "`)
                        .map((value) =>
                          str.includes(",") ? (
                            <Text>{value.replace(/,(?=[^\s])/g, ", ")}</Text>
                          ) : (
                            <Text>{value}</Text>
                          )
                        )}
                    </Text>
                  </View>
                );
              })}
            </View>
            {/********** WORK DETAILS ***************/}
            <View style={styles.heroDetailsContainer}>
              <Text style={{ ...styles.h2 }}>Work</Text>
              <Divider
                orientation="horizontal"
                width={2}
                inset={true}
                style={styles.divider}
                color={COLORS.navy}
              />
              {Object.entries(hero.work).map(([key, value, index]) => {
                // console.log(`${key}: ${value}`);
                let str = value;

                if (key != "base") {
                  str = str.replace(/,|;\s*(?![^()]*\))/g, "\n\u2022 ");
                }

                return (
                  <View
                    key={index}
                    style={{
                      flexDirection: key == "base" ? "row" : "column",
                      justifyContent: "space-between",
                      alignItems: key == "base" ? "stretch" : "flex-start",
                      flexWrap: "wrap",
                      marginBottom: 5,
                    }}
                  >
                    <Text style={{ ...styles.h4, marginBottom: 4 }}>
                      {key}:
                    </Text>

                    {str.split(`/,[s]*/g, ", "`).map((value) => (
                      <Text
                        style={{
                          ...styles.p,
                          textTransform: "capitalize",
                          lineHeight: key == "occupation" ? 20 : 0,
                          marginTop: -2,
                        }}
                      >
                        {key == "base"
                          ? value
                          : "\u2022 " + value && value != "-"
                          ? "\u2022 " + value
                          : "unknown"}
                      </Text>
                    ))}
                  </View>
                );
              })}
            </View>
            {/********** CONNECTIONS DETAILS ***************/}
            <View style={styles.heroDetailsContainer}>
              <Text style={{ ...styles.h2 }}>Connections</Text>
              <Divider
                orientation="horizontal"
                width={2}
                inset={true}
                style={styles.divider}
                color={COLORS.navy}
              />
              {Object.entries(hero.connections).map(([key, value, index]) => {
                // console.log(`${key}: ${value}`);
                let str = value.toString();
                if (key == "group-affiliation") {
                  str = value.replace(/,|;\s*(?![^()]*\))/g, "\n\u2022 ");
                } else {
                  str = value.replace(/,|;\s*(?![^()]*\))/g, "\n\u2022 ");
                }
                // const firstLetter = str.charAt(0).toUpperCase() + str.slice(1);
                return (
                  <View
                    key={index}
                    style={{
                      flexDirection: "column",
                      justifyContent: "space-between",
                      alignItems: "flex-start",
                      flexWrap: "wrap",
                      marginBottom: 5,
                    }}
                  >
                    <Text style={styles.h4}>{key}:</Text>

                    {str.split(`/,[s]*/g, ", "`).map((value) => (
                      <Text
                        style={{
                          ...styles.p,
                          textTransform: "capitalize",
                          lineHeight: 24,
                        }}
                      >
                        {value != "-" ? "\u2022 " + value : "unknown"}
                      </Text>
                    ))}
                  </View>
                );
              })}
            </View>
          </ScrollView>
          <LinearGradient
            colors={["#ffffff00", COLORS.beige]}
            style={styles.bottomFadeInfo}
            locations={[0.8, 1]}
            pointerEvents={"none"}
          />
        </View>
      </SafeAreaView>
    </View>
  );
}
Example #26
Source File: HomeScreen.js    From hero with MIT License 4 votes vote down vote up
HomeScreen = ({ navigation }) => {
  const [XMen, popularHeroes, villains] = useContext(HeroesContext);
  const [loading, setLoading] = useState(false);

  const insets = useSafeAreaInsets();

  const scrollY = new Animated.Value(0);

  const translateY = scrollY.interpolate({
    inputRange: [40, 100 + insets.top],
    outputRange: [40, insets.top - 100],
    extrapolate: "clamp",
  });

  const search = async (item) => {
    try {
      setLoading(true);
      const searchResponse = await fetch(
        `https://superheroapi.com/api/${api.key}/${item.id}/`
      );
      const characterResponse = await fetch(
        `https://comicvine.gamespot.com/api/characters/?api_key=${apiComicVine.key}&filter=name:${item.title},publisher${item.publisher}&field_list=deck,publisher,first_appeared_in_issue&format=json`
      );
      const hero = await searchResponse.json();
      const characterInfo = await characterResponse.json();
      summary = characterInfo.results[0].deck;
      firstIssue = characterInfo.results[0].first_appeared_in_issue;
      publisher = characterInfo.results[0].publisher.name;
      const firstComicResponse = await fetch(
        `https://comicvine.gamespot.com/api/issue/4000-${firstIssue.id}/?api_key=${apiComicVine.key}&format=json`
      );
      const firstComicInfo = await firstComicResponse.json();
      firstIssueURL = firstComicInfo.results.image.original_url;
      navigation.navigate("Character", {
        hero: hero,
        image: item.image,
        // publisher: item.publisher,
        comicPicture: comicPicture,
        summary: summary,
        firstIssue: firstIssue,
        firstIssueURL: firstIssueURL,
        publisher: publisher,
      });
      // setLoading(false);
    } catch (error) {
      console.error(error);
      setLoading(false);
    }
  };

  const _renderItem = ({ item, index }) => {
    return (
      <Pressable
        key={index}
        style={({ pressed }) => [
          styles.heroCard,
          { opacity: pressed ? 0.8 : 1.0 },
        ]}
        style={styles.heroCard}
      >
        <TouchableScale
          delayPressIn={50}
          activeScale={0.9}
          tension={160}
          friction={2}
          onPress={() => {
            search(item);
            // console.log(item.id);
          }}
        >
          <MaskedView
            maskElement={
              <SquircleView
                style={StyleSheet.absoluteFill}
                squircleParams={{
                  cornerRadius: 50,
                  cornerSmoothing: 1,
                  fillColor: "pink",
                }}
              />
            }
          >
            <Image
              source={item.image}
              resizeMode="cover"
              PlaceholderContent={<ActivityIndicator />}
              style={{
                width: "100%",
                height: "100%",
              }}
            />

            <View
              style={{
                flex: 1,
                position: "absolute",
                bottom: -5,
                padding: 30,
                width: "100%",
                justifyContent: "center",
                borderRadius: 20,
              }}
            >
              <Text
                style={{
                  ...styles.h4,
                  fontSize: 20,
                  color: COLORS.beige,
                  textShadowColor: "rgba(0, 0, 0, 1)",
                  textShadowOffset: { width: -1, height: 1 },
                  textShadowRadius: 5,
                }}
              >
                {item.title}
              </Text>
            </View>
          </MaskedView>
        </TouchableScale>
      </Pressable>
      // </Animated.View>
    );
  };

  useEffect(() => {
    setLoading(false);
  }, []);

  useEffect(() => {
    const unsubscribe = navigation.addListener("blur", () => {
      // Screen was blurred
      // Do something
      setLoading(false);
    });

    return unsubscribe;
  }, [navigation]);

  return (
    <>
      <View style={styles.appContainer}>
        <StatusBar
          translucent
          backgroundColor="transparent"
          barStyle="dark-content"
        />
        <SafeAreaView
          style={{
            flex: 1,
            width: Dimensions.get("window").width,
          }}
          forceInset={{ top: "always" }}
        >
          <Animated.View
            style={{
              position: "absolute",
              top: 0,
              left: 0,
              right: 0,
              zIndex: 10,
              height: 100,
              transform: [{ translateY: translateY }],
            }}
          >
            <View style={styles.header}>
              <View style={{ justifyContent: "flex-end" }}>
                <Text style={styles.appTitle}>hero</Text>
                <Text
                  style={{ ...styles.p, fontSize: 7, marginTop: -2, left: -2 }}
                >
                  the Superhero Encyclopedia
                </Text>
              </View>
            </View>
          </Animated.View>
          <ScrollView
            contentContainerStyle={{
              paddingBottom: 80,
              width: Dimensions.get("window").width,
            }}
            onScroll={(e) => {
              scrollY.setValue(e.nativeEvent.contentOffset.y);
            }}
            scrollEventThrottle={6}
          >
            <View style={styles.popularContainer}>
              <View
                style={{
                  flexDirection: "row",
                  flex: 1,
                  justifyContent: "flex-start",
                  alignItems: "center",
                }}
              >
                <Text
                  style={{
                    ...styles.h4,
                    marginBottom: 10,
                    paddingLeft: 15,
                  }}
                >
                  Popular
                </Text>

                <Icon
                  name="trending-up"
                  type="feather"
                  color={COLORS.navy}
                  size={30}
                  iconStyle={{ bottom: 2, paddingLeft: 5 }}
                />
              </View>

              <Carousel
                data={popularHeroes}
                sliderWidth={380}
                itemWidth={260}
                renderItem={_renderItem}
                loop={true}
                inactiveSlideShift={0}
                inactiveSlideOpacity={Platform.OS === "ios" ? 0.5 : 1}
              />
            </View>
            <View style={styles.heroContainer}>
              <View
                style={{
                  flexDirection: "row",
                  flex: 1,
                  justifyContent: "flex-start",
                  alignItems: "center",
                }}
              >
                <Text
                  style={{
                    ...styles.h4,
                    marginBottom: 10,
                    paddingLeft: 15,
                  }}
                >
                  Villians
                </Text>

                <Icon
                  name="emoticon-devil"
                  type="material-community"
                  color={COLORS.navy}
                  size={30}
                  iconStyle={{ bottom: 2, paddingLeft: 5, opacity: 0.9 }}
                />
              </View>

              <Carousel
                data={villains}
                sliderWidth={380}
                itemWidth={260}
                renderItem={_renderItem}
                loop={true}
                // inactiveSlideShift={-24}
                inactiveSlideOpacity={Platform.OS === "ios" ? 0.5 : 1}
              />
            </View>
            <View style={styles.heroContainer}>
              <Text
                style={{
                  ...styles.h4,
                  marginBottom: 10,
                  paddingHorizontal: 15,
                }}
              >
                X-Men
              </Text>

              <Carousel
                data={XMen}
                sliderWidth={380}
                itemWidth={260}
                renderItem={_renderItem}
                loop={true}
                // inactiveSlideShift={-24}
                inactiveSlideOpacity={Platform.OS === "ios" ? 0.5 : 1}
              />
            </View>
          </ScrollView>
          {/* )} */}
          <LinearGradient
            colors={[COLORS.beige, "#ffffff00"]}
            style={styles.scrollGradient}
            locations={[0, 1]}
            pointerEvents={"none"}
          />
        </SafeAreaView>
      </View>
      {loading === true ? (
        <Modal statusBarTranslucent={true}>
          <View
            style={{
              backgroundColor: COLORS.beige,
              width: "100%",
              height: "100%",
              justifyContent: "center",
              alignItems: "center",
            }}
          >
            <Progress.CircleSnail
              color={[COLORS.navy, COLORS.orange, COLORS.blue]}
              size={80}
              thickness={10}
              style={styles.loading}
              strokeCap={"round"}
            />
            <Text
              style={{
                ...styles.p,
                fontFamily: "Flame-Regular",
                marginTop: -15,
                left: 3,
              }}
            >
              loading...
            </Text>
          </View>
        </Modal>
      ) : null}
    </>
  );
}
Example #27
Source File: SearchScreen.js    From hero with MIT License 4 votes vote down vote up
SearchScreen = ({ navigation }) => {
  const [query, setQuery] = useState("");
  const [XMen, popularHeroes, villains] = useContext(HeroesContext);
  const [heroNames, setHeroNames] = useState([]);
  const [filteredHeroNames, setFilteredHeroNames] = useState([]);
  const [loadingSearch, setLoadingSearch] = useState(false);
  const [loading, setLoading] = useState(false);

  const fetchHeroes = async () => {
    try {
      setLoadingSearch(true);
      const response = await fetch(
        "https://cdn.jsdelivr.net/gh/akabab/[email protected]/api/all.json"
      );
      const herojson = await response.json();

      setHeroNames(herojson);
      // heroNames.forEach((hero) => {
      //   console.log(hero.name);
      // });
      setLoadingSearch(false);
    } catch (error) {
      console.error(error);
      setLoadingSearch(false);
    }
  };

  const renderFooter = () => {
    if (!loadingSearch) return null;

    return (
      <View
        style={{
          paddingVertical: 20,
          borderTopWidth: 1,
          borderColor: "#CED0CE",
        }}
      >
        <ActivityIndicator animating size="large" />
      </View>
    );
  };

  const renderSeparator = () => {
    return (
      <View
        style={{
          height: 1,
          width: "86%",
          backgroundColor: COLORS.navy,
          borderRadius: 50,
          marginLeft: "7%",
        }}
      />
    );
  };

  const handleSearch = (text) => {
    const formattedQuery = text.toLowerCase();
    const data = heroNames.filter((item) => {
      // return contains(name, formattedQuery);
      return item.name.toLowerCase().includes(formattedQuery);
    });
    setFilteredHeroNames(data);
    setQuery(text);
    // this.setState({ data, query: text })
  };

  const search = async (item) => {
    try {
      setLoading(true);
      const searchResponse = await fetch(
        `https://superheroapi.com/api/${api.key}/${item.id}/`,
        {
          method: "GET",
        }
      );
      // console.log(item.biography.fullName);
      // const realName = item.biography.fullName.text();
      const characterResponse = await fetch(
        `https://comicvine.gamespot.com/api/characters/?api_key=${apiComicVine.key}&filter=name:${item.name},publisher:${item.biography.publisher},real_name:${item.biography.fullName},origin:${item.appearance.race}&field_list=deck,publisher,first_appeared_in_issue&format=json`,
        {
          method: "GET",
        }
      );
      const hero = await searchResponse.json();
      const characterInfo = await characterResponse.json();
      // console.log(characterInfo);
      {
        characterInfo.results[0].deck == undefined
          ? (summary = "no summary")
          : (summary = characterInfo.results[0].deck);
      }
      // summary = characterInfo.results[0].deck;
      firstIssue = characterInfo.results[0].first_appeared_in_issue;
      publisher = characterInfo.results[0].publisher.name;
      const firstComicResponse = await fetch(
        `https://comicvine.gamespot.com/api/issue/4000-${firstIssue.id}/?api_key=${apiComicVine.key}&format=json`,
        {
          method: "GET",
        }
      );
      const firstComicInfo = await firstComicResponse.json();
      // console.log(firstComicInfo);
      firstIssueURL = firstComicInfo.results.image.original_url;
      navigation.navigate("Character", {
        hero: hero,
        image: { uri: item.images.lg },
        // publisher: item.publisher,
        comicPicture: comicPicture,
        summary: summary,
        firstIssue: firstIssue,
        firstIssueURL: firstIssueURL,
        publisher: publisher,
      });
      setLoading(false);
    } catch (error) {
      console.error(error);
      setLoading(false);
      Alert.alert("Sorry!", "Hero Not Found");
    }
  };

  useEffect(() => {
    fetchHeroes();
    setLoading(false);
  }, []);

  return (
    <View style={styles.appContainer}>
      <StatusBar
        translucent
        backgroundColor="transparent"
        barStyle="dark-content"
      />
      <SafeAreaView>
        <View style={styles.header}>
          <Text style={styles.appTitle}>search</Text>
        </View>
        {/* <KeyboardAvoidingView
          style={{
            paddingHorizontal: 15,
            marginBottom: -20,
            zIndex: 5,
            backgroundColor: "transparent",
          }}
        > */}
        <SearchBar
          placeholder="Search..."
          onChangeText={handleSearch}
          value={query}
          containerStyle={styles.inputContainer}
          inputContainerStyle={styles.input}
          inputStyle={styles.inputText}
          searchIcon={{ size: 25 }}
          round={true}
        />
        {/* </KeyboardAvoidingView> */}
        <BigList
          itemHeight={ITEM_HEIGHT}
          headerHeight={0}
          footerHeight={0}
          style={{
            position: "absolute",
            width: "100%",
            marginTop: 20,
            paddingTop: 10,
            height: Platform.OS === "ios" ? 580 : 590,
          }}
          data={
            filteredHeroNames && filteredHeroNames.length > 0
              ? filteredHeroNames
              : heroNames
          }
          renderItem={({ item }) => (
            <TouchableOpacity onPress={() => search(item)}>
              <View
                style={{
                  flexDirection: "row",
                  padding: 10,
                  // paddingTop: 30,
                  paddingHorizontal: 15,
                  alignItems: "center",
                  borderBottomColor: COLORS.navy,
                  borderBottomWidth: 2,

                  // backgroundColor: COLORS.navy,
                  marginBottom: 15,
                  // marginHorizontal: 10,
                  borderRadius: 15,
                }}
              >
                <Avatar
                  rounded
                  source={{ uri: item.images.md }}
                  size="medium"
                  containerStyle={{
                    marginRight: 13,
                    borderColor: COLORS.navy,
                    borderWidth: 2,
                    top: 0,
                  }}
                />
                {/* <Image
                  source={{ uri: item.images.sm }}
                  style={{ width: "100%", height: 90, borderRadius: 10 }}
                /> */}
                <Text
                  style={{
                    ...styles.p,
                    fontFamily: "Flame-Regular",
                    color: COLORS.navy,
                  }}
                >
                  {item.name}
                </Text>
              </View>
            </TouchableOpacity>
          )}
          keyExtractor={(item) => item.id.toString()}
          // ItemSeparatorComponent={renderSeparator}
          renderFooter={renderFooter}
          // ListHeaderComponent={}
        />
        <LinearGradient
          colors={[COLORS.beige, "#ffffff00"]}
          style={styles.scrollGradient}
          locations={[0, 1]}
          pointerEvents={"none"}
        />
        {loading === true ? (
          <Modal statusBarTranslucent={true}>
            <View
              style={{
                backgroundColor: COLORS.beige,
                width: "100%",
                height: "100%",
                justifyContent: "center",
                alignItems: "center",
              }}
            >
              <Progress.CircleSnail
                color={[COLORS.navy, COLORS.orange, COLORS.blue]}
                size={80}
                thickness={10}
                style={styles.loading}
                strokeCap={"round"}
              />
              <Text
                style={{
                  ...styles.p,
                  fontFamily: "Flame-Regular",
                  marginTop: -15,
                  left: 3,
                }}
              >
                loading...
              </Text>
            </View>
          </Modal>
        ) : null}
      </SafeAreaView>
    </View>
  );
}
Example #28
Source File: Post.js    From reddit-clone with MIT License 4 votes vote down vote up
Post = ({
  index,
  postId,
  userId,
  score,
  type,
  title,
  author,
  category,
  text,
  comments,
  created,
  url,
  votes,
  views,
  setIsLoaading,
  setData,
  postType,
  deleteButton,
  deletePost
}) => {
  const { colors } = useTheme()
  const navigation = useNavigation()
  const { authState } = React.useContext(AuthContext)
  const route = useRoute()

  const isUpVoted = () => {
    return votes.find(v => v.user === userId)?.vote === 1
  }

  const isDownVoted = () => {
    return votes.find(v => v.user === userId)?.vote === -1
  }

  const upVote = async () => {
    setIsLoaading(true)
    const { data } = await axios.get(`post/${postId}/upvote`)
    if (postType !== 'item') {
      setData(prevData => {
        prevData[index] = data
        return prevData
      })
    } else {
      setData(data)
    }
    setIsLoaading(false)
  }

  const downVote = async () => {
    setIsLoaading(true)
    const { data } = await axios.get(`post/${postId}/downvote`)
    if (postType !== 'item') {
      setData(prevData => {
        prevData[index] = data
        return prevData
      })
    } else {
      setData(data)
    }
    setIsLoaading(false)
  }

  const unVote = async () => {
    setIsLoaading(true)
    const { data } = await axios.get(`post/${postId}/unvote`)
    if (postType !== 'item') {
      setData(prevData => {
        prevData[index] = data
        return prevData
      })
    } else {
      setData(data)
    }
    setIsLoaading(false)
  }

  return (
    <View
      as={SafeAreaView}
      style={[
        styles.container,
        { backgroundColor: colors.bgColor, borderColor: colors.postBorder }
      ]}
    >
      <View style={styles.headerContainer}>
        <View style={styles.headerLeft}>
          <Text style={[styles.regularFont, { color: colors.text }]}>{category} </Text>
          <Text
            style={[styles.italicFont, { color: colors.blue }]}
            onPress={() => navigation.navigate('User', { username: author.username })}
          >
            @{author?.username} ·{' '}
          </Text>
          <Text style={[styles.dateText, { color: colors.text }]}>{moment(created).fromNow()}</Text>
        </View>
        <View style={styles.headerRight}>
          {deleteButton && author?.id === authState.userInfo.id && (
            <TouchableOpacity style={styles.trash} activeOpacity={0.5} onPress={deletePost}>
              <Trash color={colors.red} width={20} height={20} />
            </TouchableOpacity>
          )}
        </View>
      </View>
      <Text
        style={[styles.title, { color: colors.text }]}
        onPress={() => navigation.navigate('PostDetail', { postId, category, comments })}
      >
        {title}
      </Text>
      <Text
        numberOfLines={route.name === 'PostDetail' ? 10000 : 10}
        style={[
          styles.regularFont,
          { color: colors.text },
          type === 'link' && route.name === 'PostDetail' && styles.link
        ]}
        onPress={() =>
          route.name === 'PostDetail' && type === 'link'
            ? Linking.openURL(url)
            : navigation.navigate('PostDetail', { postId, category, comments })
        }
      >
        {type === 'link' ? url : text}
      </Text>
      <View style={styles.bottomContainer}>
        <View style={styles.centerAlign}>
          <TouchableOpacity onPress={() => (isUpVoted() ? unVote() : upVote())}>
            <ArrowUp
              width={22}
              height={22}
              strokeWidth={4}
              color={isUpVoted() ? colors.green : colors.icon}
            />
          </TouchableOpacity>
          <Text style={[styles.score, { color: colors.text }]}>{score}</Text>
          <TouchableOpacity onPress={() => (isDownVoted() ? unVote() : downVote())}>
            <ArrowDown
              width={22}
              height={22}
              strokeWidth={4}
              color={isDownVoted() ? colors.red : colors.icon}
            />
          </TouchableOpacity>
        </View>
        <TouchableOpacity
          style={styles.centerAlign}
          activeOpacity={0.7}
          onPress={() => navigation.navigate('PostDetail', { postId, category, comments })}
        >
          <MessageSquare
            color={colors.icon}
            style={styles.commentIcon}
            width={20}
            height={20}
            strokeWidth={3}
          />
          <Text style={[styles.commentText, { color: colors.text }]}>{comments?.length}</Text>
        </TouchableOpacity>
        <Text style={[styles.italicFont, { color: colors.text }]}>{views} views</Text>
      </View>
    </View>
  )
}
Example #29
Source File: CreatePost.js    From reddit-clone with MIT License 4 votes vote down vote up
CreatePost = () => {
  const { colors } = useTheme()
  const [isLoading, setIsLoading] = React.useState(false)
  const [message, setMessage] = React.useState(null)
  const fadeAnim = React.useRef(new Animated.Value(0)).current

  const fadeIn = () => {
    Animated.timing(fadeAnim, {
      toValue: 1,
      duration: 2000,
      useNativeDriver: true
    }).start()

    setTimeout(() => {
      setMessage(null)
    }, 6000)
  }

  return (
    <ScrollView as={SafeAreaView} style={[styles.container, { backgroundColor: colors.bgColor }]}>
      <Formik
        initialValues={{
          type: 'text',
          category: '',
          title: '',
          url: '',
          text: ''
        }}
        onSubmit={async (values, { setStatus, resetForm }) => {
          setIsLoading(true)
          try {
            await axios.post('posts', values)
            resetForm({ ...values, type: 'text' })
            setMessage('Successfully Created!')
            fadeIn()
          } catch (error) {
            setStatus(error.response.data.message)
          }
          setIsLoading(false)
        }}
        validationSchema={Yup.object({
          type: Yup.mixed().oneOf(['text', 'link']),
          category: Yup.string().required('Required'),
          title: Yup.string()
            .required('Required')
            .max(100, 'Must be at most 100 characters long'),
          text: Yup.string().when('type', {
            is: 'text',
            then: Yup.string()
              .required('Required')
              .min(4, 'Must be at least 4 characters long')
          }),
          url: Yup.string().when('type', {
            is: 'link',
            then: Yup.string()
              .required('Required')
              .url('Invalid Url')
          })
        })}
      >
        {({
          handleChange,
          handleBlur,
          handleSubmit,
          touched,
          errors,
          status,
          values,
          setFieldValue
        }) => (
          <View>
            {message && (
              <Animated.View
                style={{
                  opacity: fadeAnim
                }}
              >
                {!!message && <Text style={styles.message}>{message}</Text>}
              </Animated.View>
            )}
            {!!status && <Text style={styles.status}>{status}</Text>}
            <View style={styles.flexRow}>
              <Text style={[styles.formLabel, { color: colors.text }]}>Type</Text>
              {touched.type && errors.type && (
                <Text style={styles.errorMessage}>{errors.type}</Text>
              )}
            </View>
            <TypeSwichContainer>
              <TypeSwichButton selected={values.type} onClick={setFieldValue} type="text" />
              <TypeSwichButton selected={values.type} onClick={setFieldValue} type="link" />
            </TypeSwichContainer>
            <View style={styles.flexRow}>
              <Text style={[styles.formLabel, { color: colors.text }]}>Category</Text>
              {touched.category && errors.category && (
                <Text style={styles.errorMessage}>{errors.category}</Text>
              )}
            </View>
            <CategoryPicker selectedCategory={values.category} setFieldValue={setFieldValue} />

            <View style={styles.flexRow}>
              <Text style={[styles.formLabel, { color: colors.text }]}>Title</Text>
              {touched.title && errors.title && (
                <Text style={styles.errorMessage}>{errors.title}</Text>
              )}
            </View>
            <TextInput
              style={[
                styles.textInput,
                { borderColor: colors.border, color: colors.text, height: 40 },
                touched.title && errors.title && { borderColor: colors.red }
              ]}
              value={values.title}
              onChangeText={handleChange('title')}
              onBlur={handleBlur('title')}
            />

            {values.type === 'link' ? (
              <>
                <View style={styles.flexRow}>
                  <Text style={[styles.formLabel, { color: colors.text }]}>Url</Text>
                  {touched.url && errors.url && (
                    <Text style={styles.errorMessage}>{errors.url}</Text>
                  )}
                </View>
                <TextInput
                  style={[
                    styles.textInput,
                    { borderColor: colors.border, color: colors.text },
                    touched.url && errors.url && { borderColor: colors.red }
                  ]}
                  multiline
                  value={values.url}
                  onChangeText={handleChange('url')}
                  onBlur={handleBlur('url')}
                />
              </>
            ) : (
              <>
                <View style={styles.flexRow}>
                  <Text style={[styles.formLabel, { color: colors.text }]}>Text</Text>
                  {touched.text && errors.text && (
                    <Text style={styles.errorMessage}>{errors.text}</Text>
                  )}
                </View>
                <TextInput
                  style={[
                    styles.textInput,
                    { borderColor: colors.border, color: colors.text },
                    touched.text && errors.text && { borderColor: colors.red }
                  ]}
                  multiline
                  value={values.text}
                  onChangeText={handleChange('text')}
                  onBlur={handleBlur('text')}
                />
              </>
            )}
            <View style={styles.buttonContainer}>
              <TouchableOpacity
                style={[styles.submitButton, { backgroundColor: colors.blue }]}
                onPress={handleSubmit}
              >
                {isLoading ? (
                  <ActivityIndicator size="small" color="white" />
                ) : (
                  <Plus color="white" />
                )}
                <Text style={styles.submitButtonText}>Create Post</Text>
              </TouchableOpacity>
            </View>
          </View>
        )}
      </Formik>
    </ScrollView>
  )
}