Compare commits
11 Commits
52e8f8f59b
...
7c4ad599e8
Author | SHA1 | Date |
---|---|---|
Nat | 7c4ad599e8 | |
Nat | c15d42f081 | |
Nat | db54b13fd2 | |
Nat | 7f99ac8845 | |
Nat | eb9f047a04 | |
Nat | 04fb46404f | |
Nat | 68b6622cc2 | |
Nat | f889540ba5 | |
Nat | 00414df2a3 | |
Nat | 985cbe6ca1 | |
smadpro | d408c139a9 |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1.2 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><title>Bookmark</title><path d="M352 48H160a48 48 0 00-48 48v368l144-128 144 128V96a48 48 0 00-48-48z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/></svg>
|
After Width: | Height: | Size: 287 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.7 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M272 416c17.7 0 32-14.3 32-32s-14.3-32-32-32H160c-17.7 0-32-14.3-32-32V192h32c12.9 0 24.6-7.8 29.6-19.8s2.2-25.7-6.9-34.9l-64-64c-12.5-12.5-32.8-12.5-45.3 0l-64 64c-9.2 9.2-11.9 22.9-6.9 34.9s16.6 19.8 29.6 19.8l32 0 0 128c0 53 43 96 96 96H272zM304 96c-17.7 0-32 14.3-32 32s14.3 32 32 32l112 0c17.7 0 32 14.3 32 32l0 128H416c-12.9 0-24.6 7.8-29.6 19.8s-2.2 25.7 6.9 34.9l64 64c12.5 12.5 32.8 12.5 45.3 0l64-64c9.2-9.2 11.9-22.9 6.9-34.9s-16.6-19.8-29.6-19.8l-32 0V192c0-53-43-96-96-96L304 96z"/></svg>
|
After Width: | Height: | Size: 740 B |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.8 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><title>Camera</title><path d="M350.54 148.68l-26.62-42.06C318.31 100.08 310.62 96 302 96h-92c-8.62 0-16.31 4.08-21.92 10.62l-26.62 42.06C155.85 155.23 148.62 160 140 160H80a32 32 0 00-32 32v192a32 32 0 0032 32h352a32 32 0 0032-32V192a32 32 0 00-32-32h-59c-8.65 0-16.85-4.77-22.46-11.32z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/><circle cx="256" cy="272" r="80" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M124 158v-22h-24v22"/></svg>
|
After Width: | Height: | Size: 711 B |
After Width: | Height: | Size: 1.2 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><title>Checkbox</title><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M352 176L217.6 336 160 272"/><rect x="64" y="64" width="384" height="384" rx="48" ry="48" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/></svg>
|
After Width: | Height: | Size: 382 B |
After Width: | Height: | Size: 680 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><title>Close</title><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M368 368L144 144M368 144L144 368"/></svg>
|
After Width: | Height: | Size: 247 B |
After Width: | Height: | Size: 959 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><title>Create</title><path d="M384 224v184a40 40 0 01-40 40H104a40 40 0 01-40-40V168a40 40 0 0140-40h167.48" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/><path d="M459.94 53.25a16.06 16.06 0 00-23.22-.56L424.35 65a8 8 0 000 11.31l11.34 11.32a8 8 0 0011.34 0l12.06-12c6.1-6.09 6.67-16.01.85-22.38zM399.34 90L218.82 270.2a9 9 0 00-2.31 3.93L208.16 299a3.91 3.91 0 004.86 4.86l24.85-8.35a9 9 0 003.93-2.31L422 112.66a9 9 0 000-12.66l-9.95-10a9 9 0 00-12.71 0z"/></svg>
|
After Width: | Height: | Size: 598 B |
After Width: | Height: | Size: 546 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><title>Ellipsis Horizontal</title><circle cx="256" cy="256" r="32" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/><circle cx="416" cy="256" r="32" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/><circle cx="96" cy="256" r="32" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/></svg>
|
After Width: | Height: | Size: 444 B |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.6 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><title>Home</title><path d="M80 212v236a16 16 0 0016 16h96V328a24 24 0 0124-24h80a24 24 0 0124 24v136h96a16 16 0 0016-16V212" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/><path d="M480 256L266.89 52c-5-5.28-16.69-5.34-21.78 0L32 256M400 179V64h-48v69" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/></svg>
|
After Width: | Height: | Size: 491 B |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 2.3 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M181.3 32.4c17.4 2.9 29.2 19.4 26.3 36.8L197.8 128h95.1l11.5-69.3c2.9-17.4 19.4-29.2 36.8-26.3s29.2 19.4 26.3 36.8L357.8 128H416c17.7 0 32 14.3 32 32s-14.3 32-32 32H347.1L325.8 320H384c17.7 0 32 14.3 32 32s-14.3 32-32 32H315.1l-11.5 69.3c-2.9 17.4-19.4 29.2-36.8 26.3s-29.2-19.4-26.3-36.8l9.8-58.7H155.1l-11.5 69.3c-2.9 17.4-19.4 29.2-36.8 26.3s-29.2-19.4-26.3-36.8L90.2 384H32c-17.7 0-32-14.3-32-32s14.3-32 32-32h68.9l21.3-128H64c-17.7 0-32-14.3-32-32s14.3-32 32-32h68.9l11.5-69.3c2.9-17.4 19.4-29.2 36.8-26.3zM187.1 192L165.8 320h95.1l21.3-128H187.1z"/></svg>
|
After Width: | Height: | Size: 800 B |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 2.2 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><title>Heart</title><path d="M352.92 80C288 80 256 144 256 144s-32-64-96.92-64c-52.76 0-94.54 44.14-95.08 96.81-1.1 109.33 86.73 187.08 183 252.42a16 16 0 0018 0c96.26-65.34 184.09-143.09 183-252.42-.54-52.67-42.32-96.81-95.08-96.81z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/></svg>
|
After Width: | Height: | Size: 419 B |
After Width: | Height: | Size: 997 B |
After Width: | Height: | Size: 1.2 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><title>Lock Closed</title><path d="M336 208v-95a80 80 0 00-160 0v95" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/><rect x="96" y="208" width="320" height="272" rx="48" ry="48" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/></svg>
|
After Width: | Height: | Size: 415 B |
After Width: | Height: | Size: 1008 B |
After Width: | Height: | Size: 1.2 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><title>Lock Open</title><path d="M336 112a80 80 0 00-160 0v96" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/><rect x="96" y="208" width="320" height="272" rx="48" ry="48" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/></svg>
|
After Width: | Height: | Size: 409 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.4 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><title>Mail</title><rect x="48" y="96" width="416" height="320" rx="40" ry="40" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M112 160l144 112 144-112"/></svg>
|
After Width: | Height: | Size: 399 B |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 2.2 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><title>Paper Plane</title><path d="M53.12 199.94l400-151.39a8 8 0 0110.33 10.33l-151.39 400a8 8 0 01-15-.34l-67.4-166.09a16 16 0 00-10.11-10.11L53.46 215a8 8 0 01-.34-15.06zM460 52L227 285" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/></svg>
|
After Width: | Height: | Size: 374 B |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 2.1 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><title>Person</title><path d="M344 144c-3.92 52.87-44 96-88 96s-84.15-43.12-88-96c-4-55 35-96 88-96s92 42 88 96z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/><path d="M256 304c-87 0-175.3 48-191.64 138.6C62.39 453.52 68.57 464 80 464h352c11.44 0 17.62-10.48 15.65-21.4C431.3 352 343 304 256 304z" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/></svg>
|
After Width: | Height: | Size: 513 B |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 2.6 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><title>Planet</title><path d="M413.48 284.46c58.87 47.24 91.61 89 80.31 108.55-17.85 30.85-138.78-5.48-270.1-81.15S.37 149.84 18.21 119c11.16-19.28 62.58-12.32 131.64 14.09" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/><circle cx="256" cy="256" r="160" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/></svg>
|
After Width: | Height: | Size: 444 B |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 16 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><title>Search</title><path d="M221.09 64a157.09 157.09 0 10157.09 157.09A157.1 157.1 0 00221.09 64z" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="32" d="M338.29 338.29L448 448"/></svg>
|
After Width: | Height: | Size: 393 B |
After Width: | Height: | Size: 566 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><title>Square</title><path d="M416 448H96a32.09 32.09 0 01-32-32V96a32.09 32.09 0 0132-32h320a32.09 32.09 0 0132 32v320a32.09 32.09 0 01-32 32z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/></svg>
|
After Width: | Height: | Size: 329 B |
After Width: | Height: | Size: 18 KiB |
|
@ -21,9 +21,9 @@
|
|||
"expo-status-bar": "~1.3.0",
|
||||
"expo-web-browser": "~10.2.0",
|
||||
"mime": "^2.5.2",
|
||||
"react": "17.0.2",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "17.0.2",
|
||||
"react-native": "https://github.com/expo/react-native/archive/sdk-45.0.0.tar.gz",
|
||||
"react-native": "0.68.2",
|
||||
"react-native-gesture-handler": "~2.2.1",
|
||||
"react-native-pager-view": "5.4.15",
|
||||
"react-native-popup-menu": "^0.15.10",
|
||||
|
|
18
src/App.js
|
@ -7,11 +7,11 @@ import { createStackNavigator } from "@react-navigation/stack";
|
|||
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
|
||||
import { NavigationContainer } from "@react-navigation/native";
|
||||
import { MenuProvider } from "react-native-popup-menu";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
|
||||
import { registerRootComponent } from 'expo';
|
||||
import * as Linking from "expo-linking";
|
||||
|
||||
import Icon from "src/components/icons.js";
|
||||
import ViewPost from "src/components/pages/view-post";
|
||||
import ViewComments from "src/components/pages/view-comments.js";
|
||||
|
||||
|
@ -25,7 +25,6 @@ import Search from 'src/components/pages/discover/search';
|
|||
import ViewHashtag from 'src/components/pages/discover/view-hashtag';
|
||||
import Direct from "src/components/pages/direct";
|
||||
import Conversation, { Compose } from "src/components/pages/direct/conversation";
|
||||
import Notifications from 'src/components/pages/profile/notifications';
|
||||
import UserList from "src/components/pages/user-list.js";
|
||||
import Settings from "src/components/pages/profile/settings.js";
|
||||
|
||||
|
@ -41,10 +40,10 @@ const MainNavigator = () => {
|
|||
|
||||
const bottomTabIcon = name => {
|
||||
return ({ size, focused }) =>
|
||||
<Ionicons
|
||||
<Icon
|
||||
name = { name }
|
||||
size = { size }
|
||||
color = { focused ? "black" : "#666" }/>
|
||||
focused = { focused }/>
|
||||
};
|
||||
|
||||
const screenOptions = {
|
||||
|
@ -58,23 +57,23 @@ const MainNavigator = () => {
|
|||
},
|
||||
Feed: {
|
||||
tabBarAccessibilityLabel: "Feed",
|
||||
tabBarIcon: bottomTabIcon("home-outline"),
|
||||
tabBarIcon: bottomTabIcon("feed"),
|
||||
},
|
||||
Discover: {
|
||||
tabBarAccessibilityLabel: "Discover",
|
||||
tabBarIcon: bottomTabIcon("search-outline"),
|
||||
tabBarIcon: bottomTabIcon("search"),
|
||||
},
|
||||
Publish: {
|
||||
tabBarAccessibilityLabel: "Publish",
|
||||
tabBarIcon: bottomTabIcon("camera-outline"),
|
||||
tabBarIcon: bottomTabIcon("camera"),
|
||||
},
|
||||
Direct: {
|
||||
tabBarAccessibilityLabel: "Direct messages",
|
||||
tabBarIcon: bottomTabIcon("mail-outline"),
|
||||
tabBarIcon: bottomTabIcon("mail"),
|
||||
},
|
||||
Profile: {
|
||||
tabBarAccessibilityLabel: "Profile",
|
||||
tabBarIcon: bottomTabIcon("person-outline"),
|
||||
tabBarIcon: bottomTabIcon("person"),
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -143,7 +142,6 @@ const App = (props) => {
|
|||
<Stack.Screen name="ViewComments" component={ViewComments}/>
|
||||
<Stack.Screen name="ViewProfile" component={ViewProfile}/>
|
||||
<Stack.Screen name="ViewHashtag" component={ViewHashtag}/>
|
||||
<Stack.Screen name="Notifications" component={Notifications}/>
|
||||
<Stack.Screen name="UserList" component={UserList}/>
|
||||
</Stack.Navigator>
|
||||
</NavigationContainer>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import React from "react";
|
||||
import { Dimensions, View, Image } from "react-native";
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import {
|
||||
Menu,
|
||||
MenuOptions,
|
||||
|
@ -9,6 +8,8 @@ import {
|
|||
renderers
|
||||
} from "react-native-popup-menu";
|
||||
|
||||
import Icon from "src/components/icons.js";
|
||||
|
||||
const { SlideInMenu } = renderers;
|
||||
|
||||
const SCREEN_WIDTH = Dimensions.get("window").width;
|
||||
|
@ -34,10 +35,8 @@ const ContextMenu = (props) => {
|
|||
<View style = { props.containerStyle }>
|
||||
<Menu renderer = { SlideInMenu }>
|
||||
<MenuTrigger>
|
||||
<Ionicons
|
||||
name = "ellipsis-horizontal"
|
||||
size = { props.size ? props.size : 24 }
|
||||
color = { props.colour ? props.colour : "#666" } />
|
||||
<Icon name = "ellipsis"
|
||||
size = { props.size ? props.size : 24 }/>
|
||||
</MenuTrigger>
|
||||
<MenuOptions customStyles = { optionsStyles }>
|
||||
{ props.children }
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
import React from "react";
|
||||
import { Image, StyleSheet } from "react-native";
|
||||
|
||||
/* React doesn't allow you to `require` images dynamically because they need
|
||||
* to be known ahead of time. As such, we require all the icons we'll need in
|
||||
* this map. If a new icon is added, then it must be added to this array
|
||||
*/
|
||||
const images = {
|
||||
ellipsis: {
|
||||
black: require("assets/icons/ellipsis-black-64px.png"),
|
||||
},
|
||||
feed: {
|
||||
black: require("assets/icons/feed-black-64px.png"),
|
||||
grey: require("assets/icons/feed-grey-64px.png"),
|
||||
},
|
||||
search: {
|
||||
black: require("assets/icons/search-black-64px.png"),
|
||||
grey: require("assets/icons/search-grey-64px.png"),
|
||||
},
|
||||
camera: {
|
||||
black: require("assets/icons/camera-black-64px.png"),
|
||||
grey: require("assets/icons/camera-grey-64px.png"),
|
||||
},
|
||||
mail: {
|
||||
black: require("assets/icons/mail-black-64px.png"),
|
||||
grey: require("assets/icons/mail-grey-64px.png"),
|
||||
},
|
||||
person: {
|
||||
black: require("assets/icons/person-black-64px.png"),
|
||||
grey: require("assets/icons/person-grey-64px.png"),
|
||||
},
|
||||
hashtag: {
|
||||
black: require("assets/icons/hashtag-black-64px.png"),
|
||||
grey: require("assets/icons/hashtag-grey-64px.png"),
|
||||
},
|
||||
planet: {
|
||||
black: require("assets/icons/planet-black-64px.png"),
|
||||
grey: require("assets/icons/planet-grey-64px.png"),
|
||||
},
|
||||
square: {
|
||||
black: require("assets/icons/square-black-64px.png"),
|
||||
},
|
||||
checkbox: {
|
||||
black: require("assets/icons/checkbox-black-64px.png"),
|
||||
},
|
||||
"lock-closed": {
|
||||
black: require("assets/icons/lock-closed-black-64px.png"),
|
||||
grey: require("assets/icons/lock-closed-grey-64px.png"),
|
||||
},
|
||||
"lock-open": {
|
||||
black: require("assets/icons/lock-open-black-64px.png"),
|
||||
grey: require("assets/icons/lock-open-grey-64px.png"),
|
||||
},
|
||||
heart: {
|
||||
black: require("assets/icons/heart-black-64px.png"),
|
||||
grey: require("assets/icons/heart-grey-64px.png"),
|
||||
},
|
||||
bookmark: {
|
||||
black: require("assets/icons/bookmark-black-64px.png"),
|
||||
grey: require("assets/icons/bookmark-grey-64px.png"),
|
||||
},
|
||||
boost: {
|
||||
black: require("assets/icons/boost-black-64px.png"),
|
||||
grey: require("assets/icons/boost-grey-64px.png"),
|
||||
},
|
||||
create: {
|
||||
black: require("assets/icons/create-black-64px.png"),
|
||||
},
|
||||
close: {
|
||||
black: require("assets/icons/close-black-64px.png"),
|
||||
},
|
||||
};
|
||||
|
||||
const Icon = ({name, size, focused = true}) => {
|
||||
if (images[name] === undefined) {
|
||||
console.error(`Icon "${name}" is not recognized`);
|
||||
return <></>;
|
||||
}
|
||||
|
||||
// Warn the programmer if their chosen icon colour hasn't been rendered
|
||||
if (focused && images[name].black === undefined) {
|
||||
console.error(`There exists no focused version of icon "${name}"`);
|
||||
return <></>;
|
||||
}
|
||||
|
||||
if (!focused && images[name].grey === undefined) {
|
||||
console.error(`There exists no unfocused version of icon "${name}"`);
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
image: {
|
||||
width: size,
|
||||
height: size,
|
||||
},
|
||||
});
|
||||
|
||||
return <Image
|
||||
style = { styles.image }
|
||||
source = {images[name][focused ? "black" : "grey"]}/>;
|
||||
};
|
||||
|
||||
export default Icon;
|
|
@ -7,9 +7,9 @@ import {
|
|||
MenuTrigger,
|
||||
renderers
|
||||
} from "react-native-popup-menu";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
|
||||
const { SlideInMenu } = renderers;
|
||||
import Icon from "src/components/icons.js";
|
||||
|
||||
const SCREEN_WIDTH = Dimensions.get("window").width;
|
||||
|
||||
|
@ -34,10 +34,7 @@ const ModerateMenu = (props) => {
|
|||
<View style = { props.containerStyle }>
|
||||
<Menu renderer = { SlideInMenu }>
|
||||
<MenuTrigger>
|
||||
<Ionicons
|
||||
name = "ellipsis-horizontal"
|
||||
color = "#000"
|
||||
style = { props.triggerStyle }/>
|
||||
<Icon name = "ellipsis"/>
|
||||
</MenuTrigger>
|
||||
<MenuOptions customStyles = { optionsStyles }>
|
||||
<MenuOption text="Hide" />
|
||||
|
|
|
@ -125,16 +125,7 @@ const Authenticate = ({navigation}) => {
|
|||
token.access_token
|
||||
).then(resp => resp.json());
|
||||
|
||||
await AsyncStorage.multiSet([
|
||||
[ "@user_profile", JSON.stringify(profile), ],
|
||||
[ // TODO: Enable storing notifications
|
||||
"@user_notifications",
|
||||
JSON.stringify({
|
||||
unread: false,
|
||||
memory: []
|
||||
}),
|
||||
],
|
||||
]);
|
||||
await AsyncStorage.setItem("@user_profile", JSON.stringify(profile));
|
||||
|
||||
navigation.replace("Main");
|
||||
};
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
ScrollView,
|
||||
View,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
|
@ -11,9 +12,7 @@ import {
|
|||
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
import * as requests from "src/requests";
|
||||
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
|
||||
import Icon from "src/components/icons.js";
|
||||
import ModerateMenu from "src/components/moderate-menu.js";
|
||||
|
||||
const TEST_IMAGE_1 = "https://cache.desktopnexus.com/thumbseg/2255/2255124-bigthumbnail.jpg";
|
||||
|
@ -142,7 +141,7 @@ const Direct = ({ navigation }) => {
|
|||
return (
|
||||
<>
|
||||
{ state.loaded
|
||||
? <>
|
||||
? <ScrollView>
|
||||
<View style = { [ styles.row, styles.form.container ] }>
|
||||
<TextInput
|
||||
placeholder = "Search..."
|
||||
|
@ -158,7 +157,7 @@ const Direct = ({ navigation }) => {
|
|||
<TouchableOpacity
|
||||
style = { styles.form.compose }
|
||||
onPress = { () => { navigation.navigate("Compose") } }>
|
||||
<Ionicons name = "md-create" size = { 24 } color = "black"/>
|
||||
<Icon name = "create" size = { 24 }/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<>
|
||||
|
@ -182,7 +181,7 @@ const Direct = ({ navigation }) => {
|
|||
: <></>
|
||||
}
|
||||
</>
|
||||
</>
|
||||
</ScrollView>
|
||||
: <></>
|
||||
}
|
||||
</>
|
||||
|
|
|
@ -11,8 +11,7 @@ import {
|
|||
} from "react-native";
|
||||
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import Icon from "src/components/icons.js";
|
||||
|
||||
import {
|
||||
Menu,
|
||||
|
@ -70,10 +69,7 @@ const ConversationContainer = (props) => (
|
|||
<TouchableOpacity
|
||||
style = { styles.send.button }
|
||||
onPress = { props.onSubmit }>
|
||||
<Ionicons
|
||||
name = "paper-plane-outline"
|
||||
size = { 24 }
|
||||
color = "black" />
|
||||
<Icon name="paper-plane" size={24}/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
|
@ -110,7 +106,7 @@ const Compose = ({ navigation }) => {
|
|||
};
|
||||
|
||||
const Conversation = ({ navigation }) => {
|
||||
const conversation = navigation.getParam("conversation", {});
|
||||
const conversation = route.params.conversation
|
||||
const [state, setState] = useState({
|
||||
loaded: false,
|
||||
newMessage: "",
|
||||
|
|
|
@ -1,13 +1,20 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { View, TextInput, Text, Dimensions } from "react-native";
|
||||
import {
|
||||
ScrollView,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
TextInput,
|
||||
Text,
|
||||
Dimensions
|
||||
} from "react-native";
|
||||
|
||||
import { TabView, TabBar, SceneMap } from "react-native-tab-view";
|
||||
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
|
||||
import * as requests from "src/requests";
|
||||
|
||||
import Icon from "src/components/icons.js";
|
||||
import PagedGrid from "src/components/posts/paged-grid";
|
||||
import { TouchableWithoutFeedback } from "react-native-gesture-handler";
|
||||
|
||||
|
@ -25,11 +32,11 @@ const Discover = (props) => {
|
|||
const [ routes ] = useState([
|
||||
{
|
||||
key: "local",
|
||||
icon: "md-home",
|
||||
icon: "feed",
|
||||
},
|
||||
{
|
||||
key: "federated",
|
||||
icon: "md-planet",
|
||||
icon: "planet",
|
||||
},
|
||||
]);
|
||||
|
||||
|
@ -132,34 +139,36 @@ const Discover = (props) => {
|
|||
style = { styles.tabBar.tab } />
|
||||
);
|
||||
|
||||
const renderIcon = ({ route, color }) => (
|
||||
<Ionicons
|
||||
const renderIcon = ({ route, focused }) => (
|
||||
<Icon
|
||||
name = { route.icon }
|
||||
size = { 24 }
|
||||
color = { color } />
|
||||
focused = { focused } />
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{ state.loaded
|
||||
? <>
|
||||
<TouchableWithoutFeedback
|
||||
onPress = { () => props.navigation.navigate("Search") }>
|
||||
<View style = { styles.form }>
|
||||
<View style = { styles.searchBarContainer }>
|
||||
<Text style = { styles.searchBar }>
|
||||
Search...
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
? <ScrollView>
|
||||
<View style = { styles.form.container }>
|
||||
<TextInput
|
||||
style = { styles.form.input }
|
||||
placeholder = "Search..."
|
||||
onPressIn = {
|
||||
() => props.navigation.navigate("Search")
|
||||
}/>
|
||||
<TouchableOpacity
|
||||
style = { styles.form.submit }>
|
||||
<Icon name="search" size={24} color="black" />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<TabView
|
||||
navigationState = { { index, routes } }
|
||||
renderScene = { renderScene }
|
||||
renderTabBar = { renderTabBar }
|
||||
onIndexChange = { setIndex }
|
||||
initialLayout = { { width: SCREEN_WIDTH } } />
|
||||
</>
|
||||
</ScrollView>
|
||||
: <></>
|
||||
}
|
||||
</>
|
||||
|
@ -169,10 +178,25 @@ const Discover = (props) => {
|
|||
const SCREEN_WIDTH = Dimensions.get("window").width;
|
||||
const styles = {
|
||||
form: {
|
||||
justifyContent: "center",
|
||||
backgroundColor: "white",
|
||||
padding: 20
|
||||
container: {
|
||||
flexDirection: "row",
|
||||
justifyContent: "center",
|
||||
backgroundColor: "white",
|
||||
padding: 20,
|
||||
},
|
||||
|
||||
input: {
|
||||
flexGrow: 1,
|
||||
padding: 10,
|
||||
fontSize: 17,
|
||||
color: "#888"
|
||||
},
|
||||
|
||||
submit: {
|
||||
padding: 20,
|
||||
}
|
||||
},
|
||||
|
||||
searchBar: {
|
||||
padding: 10,
|
||||
fontSize: 17,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
ScrollView,
|
||||
View,
|
||||
TextInput,
|
||||
Text,
|
||||
|
@ -7,11 +8,11 @@ import {
|
|||
Image,
|
||||
} from "react-native";
|
||||
import { TabView, TabBar, SceneMap } from "react-native-tab-view";
|
||||
import { FontAwesome } from '@expo/vector-icons';
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
|
||||
import * as requests from "src/requests";
|
||||
import { StatusBarSpace } from "src/interface/rendering";
|
||||
import Icon from "src/components/icons.js";
|
||||
|
||||
import { TouchableOpacity } from "react-native-gesture-handler";
|
||||
|
||||
|
@ -112,7 +113,7 @@ const Search = ({navigation}) => {
|
|||
const [ routes ] = useState([
|
||||
{
|
||||
key: "accounts",
|
||||
icon: "user",
|
||||
icon: "profile",
|
||||
},
|
||||
{
|
||||
key: "hashtags",
|
||||
|
@ -151,17 +152,17 @@ const Search = ({navigation}) => {
|
|||
style = { styles.tabBar.tab } />
|
||||
);
|
||||
|
||||
const renderIcon = ({ route, color }) => (
|
||||
<FontAwesome
|
||||
const renderIcon = ({ route, focused }) => (
|
||||
<Icon
|
||||
name = { route.icon }
|
||||
size = { 24 }
|
||||
color = { color } />
|
||||
focused = { focused }/>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{ state.loaded
|
||||
? <>
|
||||
? <ScrollView>
|
||||
<View style = { styles.form.container }>
|
||||
<TextInput
|
||||
style = { styles.form.input }
|
||||
|
@ -182,7 +183,7 @@ const Search = ({navigation}) => {
|
|||
<TouchableOpacity
|
||||
onPress = { _handleSearch }
|
||||
style = { styles.form.submit }>
|
||||
<FontAwesome name="search" size={24} color="black" />
|
||||
<Icon name="search" size={24}/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
{ state.results
|
||||
|
@ -194,7 +195,7 @@ const Search = ({navigation}) => {
|
|||
initialLayout = { { width: SCREEN_WIDTH } } />
|
||||
: <></>
|
||||
}
|
||||
</>
|
||||
</ScrollView>
|
||||
: <></>
|
||||
}
|
||||
</>
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import { View, Image, Dimensions, Text } from "react-native";
|
||||
import { ScrollView, View, Image, Dimensions, Text } from "react-native";
|
||||
import PagedGrid from "src/components/posts/paged-grid";
|
||||
|
||||
import * as requests from "src/requests";
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
|
||||
const ViewHashtag = ({navigation}) => {
|
||||
const ViewHashtag = ({ navigation, route }) => {
|
||||
const FETCH_LIMIT = 18;
|
||||
let [state, setState] = useState({
|
||||
tag: navigation.getParam("tag", null),
|
||||
tag: route.params.tag,
|
||||
posts: [],
|
||||
offset: 0,
|
||||
followed: false,
|
||||
|
@ -66,10 +66,16 @@ const ViewHashtag = ({navigation}) => {
|
|||
});
|
||||
};
|
||||
|
||||
const latest = state.tag.history[0];
|
||||
// A hashtag's history describes how actively it's being used. There's
|
||||
// one element in the history array for every set interval of time.
|
||||
// state.tag.history may be undefined, and its length might be 0.
|
||||
let latest = null;
|
||||
if (state.tag.history && state.tag.history.length > 0) {
|
||||
latest = state.tag.history[0];
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<ScrollView>
|
||||
<View>
|
||||
<View style = { styles.headerContainer }>
|
||||
<View>
|
||||
|
@ -105,17 +111,19 @@ const ViewHashtag = ({navigation}) => {
|
|||
</View>
|
||||
<>
|
||||
{ state.loaded && state.posts.length > 0
|
||||
? <PagedGrid
|
||||
navigation = { navigation }
|
||||
posts = { state.posts }
|
||||
onShowMore = { _handleShowMore } />
|
||||
: <Text style = { styles.nothing }>
|
||||
Nothing to show
|
||||
</Text>
|
||||
? state.posts.length > 0
|
||||
? <PagedGrid
|
||||
navigation = { navigation }
|
||||
posts = { state.posts }
|
||||
onShowMore = { _handleShowMore } />
|
||||
: <Text style = { styles.nothing }>
|
||||
Nothing to show
|
||||
</Text>
|
||||
: <></>
|
||||
}
|
||||
</>
|
||||
</View>
|
||||
</>
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import { ScrollView, Dimensions, View, Image, Text } from "react-native";
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
|
||||
import TimelineView from "src/components/posts/timeline-view";
|
||||
import { TouchableWithoutFeedback } from "react-native-gesture-handler";
|
||||
|
@ -64,25 +63,25 @@ const Feed = (props) => {
|
|||
);
|
||||
}, []);
|
||||
|
||||
const _handleTimelineLoaded = () => setState({...state,
|
||||
postsRendered: true,
|
||||
});
|
||||
const _handleTimelineLoaded = () => {
|
||||
setState({...state,
|
||||
postsRendered: true,
|
||||
});
|
||||
};
|
||||
|
||||
let endOfTimelineMessage = <></>;
|
||||
if (state.postsRendered || state.loaded && state.posts.length == 0) {
|
||||
if (state.postsRendered) {
|
||||
// Only render the timeline interruption if all of the posts have been
|
||||
// rendered in the feed.
|
||||
endOfTimelineMessage = <>
|
||||
<View style = {
|
||||
state.posts.length == 0
|
||||
? styles.ifCaughtUp
|
||||
? styles.interruption.container
|
||||
: styles.interruption.topBorder
|
||||
}>
|
||||
<View style = { styles.interruption.inner }>
|
||||
<Ionicons
|
||||
name="ios-checkmark-circle-outline"
|
||||
size= { 150 }
|
||||
color="black" />
|
||||
<Image style = {{ width: 150, height: 150 }}
|
||||
source = { require("assets/images/checkmark-circle.png") }/>
|
||||
|
||||
<Text style = { styles.interruption.header }>
|
||||
You're all caught up.
|
||||
|
@ -103,17 +102,14 @@ const Feed = (props) => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<StatusBarSpace />
|
||||
{ state.loaded
|
||||
? state.posts.length > 0
|
||||
? <ScrollView>
|
||||
<TimelineView
|
||||
navigation = { props.navigation }
|
||||
posts = { state.posts }
|
||||
onTimelineLoaded = { _handleTimelineLoaded }/>
|
||||
{ endOfTimelineMessage }
|
||||
</ScrollView>
|
||||
: endOfTimelineMessage
|
||||
? <ScrollView contentContainerStyles = { styles.container }>
|
||||
<TimelineView
|
||||
navigation = { props.navigation }
|
||||
posts = { state.posts }
|
||||
onTimelineLoaded = { _handleTimelineLoaded }/>
|
||||
{ endOfTimelineMessage }
|
||||
</ScrollView>
|
||||
: <></>
|
||||
}
|
||||
</>
|
||||
|
@ -123,14 +119,20 @@ const Feed = (props) => {
|
|||
const screen_width = Dimensions.get("window").width;
|
||||
const screen_height = Dimensions.get("window").height;
|
||||
const styles = {
|
||||
timeline: {
|
||||
height: screen_height - (screen_height / 12)
|
||||
},
|
||||
ifCaughtUp: {
|
||||
flexGrow: 1,
|
||||
justifyContent: "center",
|
||||
},
|
||||
interruption: {
|
||||
container: {
|
||||
/* HACK: The space between the top of the screen and the bottom
|
||||
* tabs bar is about `screen_height - 100. See issue #7359 on the
|
||||
* react-navigation github repository. There is supposed to be
|
||||
* a way make a ScrollView's height at least the available viewport
|
||||
* using flexGrow, which we need as a container to use
|
||||
* justifyContent, but that doesn't seem to work here for some
|
||||
* reason. It'll be slightly off-center but the user never should
|
||||
* be able to accidentally scroll on this page.
|
||||
*/
|
||||
height: screen_height - 100,
|
||||
justifyContent: "center",
|
||||
},
|
||||
topBorder: {
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: "#CCC",
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import { ScrollView } from "react-native";
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
|
||||
import * as requests from "src/requests";
|
||||
|
@ -56,7 +57,7 @@ const OlderPosts = (props) => {
|
|||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ScrollView>
|
||||
{ state.loaded
|
||||
? <>
|
||||
<PagedGrid
|
||||
|
@ -66,7 +67,7 @@ const OlderPosts = (props) => {
|
|||
</>
|
||||
: <></>
|
||||
}
|
||||
</>
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
Text,
|
||||
TouchableOpacity,
|
||||
FlatList,
|
||||
ScrollView,
|
||||
} from "react-native";
|
||||
|
||||
import * as Linking from "expo-linking";
|
||||
|
@ -13,7 +14,12 @@ import AsyncStorage from "@react-native-async-storage/async-storage";
|
|||
|
||||
import { activeOrNot } from "src/interface/interactions";
|
||||
import HTML from "react-native-render-html";
|
||||
import { withLeadingAcct, withoutHTML, pluralize } from "src/interface/rendering";
|
||||
import {
|
||||
withLeadingAcct,
|
||||
withoutHTML,
|
||||
pluralize,
|
||||
StatusBarSpace,
|
||||
} from "src/interface/rendering";
|
||||
import * as requests from "src/requests";
|
||||
|
||||
import GridView from "src/components/posts/grid-view";
|
||||
|
@ -56,11 +62,11 @@ const HTMLLink = ({link}) => {
|
|||
}
|
||||
}
|
||||
|
||||
const ViewProfile = ({navigation}) => {
|
||||
const ViewProfile = ({ navigation, route }) => {
|
||||
// As rendered when opened from somewhere other than the tab bar
|
||||
const [state, setState] = useState({
|
||||
loaded: false,
|
||||
profile: navigation.getParam("profile"),
|
||||
profile: route.params.profile,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -159,9 +165,7 @@ const ViewProfile = ({navigation}) => {
|
|||
return (
|
||||
<>
|
||||
{ state.loaded
|
||||
? <>
|
||||
active = { navigation.getParam("originTab") }
|
||||
navigation = { navigation }>
|
||||
? <ScrollView>
|
||||
<RawProfile
|
||||
navigation = { navigation }
|
||||
onFollow = { _handleFollow }
|
||||
|
@ -172,7 +176,7 @@ const ViewProfile = ({navigation}) => {
|
|||
listedUsers = { state.listedUsers }
|
||||
followed = { state.followed }
|
||||
posts = { state.posts }/>
|
||||
</>
|
||||
</ScrollView>
|
||||
: <></>
|
||||
}
|
||||
</>
|
||||
|
@ -184,57 +188,60 @@ const Profile = ({ navigation }) => {
|
|||
loaded: false,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
let profile;
|
||||
let notifs;
|
||||
let domain;
|
||||
let accessToken;
|
||||
const init = async () => {
|
||||
const [
|
||||
profilePair,
|
||||
instancePair,
|
||||
tokenPair
|
||||
] = await AsyncStorage.multiGet([
|
||||
"@user_profile",
|
||||
"@user_instance",
|
||||
"@user_token",
|
||||
]);
|
||||
|
||||
AsyncStorage
|
||||
.multiGet([
|
||||
const profile = JSON.parse(profilePair[1]);
|
||||
const instance = instancePair[1];
|
||||
const accessToken = JSON.parse(tokenPair[1]).access_token;
|
||||
|
||||
const latestProfile =
|
||||
await requests.fetchProfile(instance, profile.id, accessToken);
|
||||
const posts =
|
||||
await requests.fetchAccountStatuses(instance, profile.id, accessToken);
|
||||
const followers =
|
||||
await requests.fetchFollowers(instance, profile.id, accessToken);
|
||||
|
||||
const latestProfileString = JSON.stringify(latestProfile);
|
||||
|
||||
// Update the profile in AsyncStorage if it's changed
|
||||
if(latestProfileString != JSON.stringify(profile)) {
|
||||
await AsyncStorage.setItem(
|
||||
"@user_profile",
|
||||
"@user_notifications",
|
||||
"@user_instance",
|
||||
"@user_token",
|
||||
])
|
||||
.then(([profilePair, notifPair, domainPair, tokenPair]) => {
|
||||
profile = JSON.parse(profilePair[1]);
|
||||
notifs = JSON.parse(notifPair[1]);
|
||||
domain = domainPair[1];
|
||||
accessToken = JSON.parse(tokenPair[1]).access_token;
|
||||
latestProfileString
|
||||
);
|
||||
}
|
||||
|
||||
return Promise.all([
|
||||
requests.fetchProfile(domain, profile.id),
|
||||
requests.fetchAccountStatuses(domain, profile.id, accessToken),
|
||||
requests.fetchFollowers(domain, profile.id, accessToken),
|
||||
]);
|
||||
})
|
||||
.then(([latestProfile, posts, followers]) => {
|
||||
if(JSON.stringify(latestProfile) != JSON.stringify(profile)) {
|
||||
profile = latestProfile
|
||||
}
|
||||
|
||||
setState({...state,
|
||||
profile: profile,
|
||||
notifs: notifs,
|
||||
posts: posts,
|
||||
setState({...state,
|
||||
profile: latestProfile,
|
||||
posts: posts,
|
||||
listedUsers: followers,
|
||||
loaded: true,
|
||||
});
|
||||
});
|
||||
}, []);
|
||||
loaded: true,
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => { init(); }, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<StatusBarSpace/>
|
||||
{ state.loaded
|
||||
? <>
|
||||
? <ScrollView>
|
||||
<RawProfile
|
||||
navigation = { navigation }
|
||||
own = { true }
|
||||
profile = { state.profile }
|
||||
posts = { state.posts }
|
||||
listedUsers = { state.listedUsers }
|
||||
notifs = { state.notifs }/>
|
||||
</>
|
||||
listedUsers = { state.listedUsers }/>
|
||||
</ScrollView>
|
||||
: <></>
|
||||
}
|
||||
</>
|
||||
|
|
|
@ -1,440 +0,0 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
|
||||
import {
|
||||
Dimensions,
|
||||
View,
|
||||
TouchableOpacity,
|
||||
Image,
|
||||
Text,
|
||||
} from "react-native";
|
||||
import { FontAwesome } from "@expo/vector-icons";
|
||||
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
|
||||
const TEST_IMAGE = "https://cache.desktopnexus.com/thumbseg/2255/2255124-bigthumbnail.jpg";
|
||||
const TEST_NOTIFICATIONS = [
|
||||
{
|
||||
id: 1,
|
||||
type: "follow",
|
||||
account: {
|
||||
acct: "njms",
|
||||
avatar: TEST_IMAGE,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
type: "follow_request",
|
||||
account: {
|
||||
acct: "njms",
|
||||
avatar: TEST_IMAGE,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
type: "mention",
|
||||
account: {
|
||||
acct: "njms",
|
||||
avatar: TEST_IMAGE,
|
||||
},
|
||||
status: {
|
||||
id: 1,
|
||||
media_attachments: [],
|
||||
content: "This is a message",
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
type: "mention",
|
||||
account: {
|
||||
acct: "njms",
|
||||
avatar: TEST_IMAGE,
|
||||
},
|
||||
status: {
|
||||
id: 1,
|
||||
media_attachments: [
|
||||
{ url: TEST_IMAGE }
|
||||
],
|
||||
content: "This is a message",
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
type: "mention",
|
||||
account: {
|
||||
acct: "njms",
|
||||
avatar: TEST_IMAGE,
|
||||
},
|
||||
status: {
|
||||
id: 1,
|
||||
media_attachments: [
|
||||
{ url: TEST_IMAGE }
|
||||
],
|
||||
content: "This is a really really really really really really"
|
||||
+ " really really really really really really long message",
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
type: "reblog",
|
||||
account: {
|
||||
acct: "njms",
|
||||
avatar: TEST_IMAGE,
|
||||
},
|
||||
status: {
|
||||
id: 1,
|
||||
media_attachments: [
|
||||
{ url: TEST_IMAGE }
|
||||
],
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
type: "favourite",
|
||||
account: {
|
||||
acct: "njms",
|
||||
avatar: TEST_IMAGE,
|
||||
},
|
||||
status: {
|
||||
id: 1,
|
||||
media_attachments: [
|
||||
{ url: TEST_IMAGE }
|
||||
],
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
type: "status",
|
||||
account: {
|
||||
acct: "njms",
|
||||
avatar: TEST_IMAGE,
|
||||
},
|
||||
status: {
|
||||
id: 1,
|
||||
media_attachments: [
|
||||
{ url: TEST_IMAGE }
|
||||
],
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
function navigateProfileFactory(nav, acct) {
|
||||
return () => {
|
||||
nav.navigate("ViewProfile", {
|
||||
acct: acct,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function navigatePostFactory(nav, id) {
|
||||
return () => {
|
||||
nav.navigate("ViewPost", {
|
||||
originTab: "Profile",
|
||||
id: id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function renderNotification(notif, navigation) {
|
||||
switch(notif.type) {
|
||||
case "follow":
|
||||
return <Follow
|
||||
data = { notif }
|
||||
key = { notif.id }
|
||||
navigation = { navigation } />
|
||||
case "follow_request":
|
||||
return <FollowRequest
|
||||
data = { notif }
|
||||
key = { notif.id }
|
||||
navigation = { navigation } />
|
||||
case "mention":
|
||||
return <Mention
|
||||
data = { notif }
|
||||
key = { notif.id }
|
||||
navigation = { navigation } />
|
||||
case "reblog":
|
||||
return <Reblog
|
||||
data = { notif }
|
||||
key = { notif.id }
|
||||
navigation = { navigation } />
|
||||
case "favourite":
|
||||
return <Favourite
|
||||
data = { notif }
|
||||
key = { notif.id }
|
||||
navigation = { navigation } />
|
||||
case "status":
|
||||
return <Status
|
||||
data = { notif }
|
||||
key = { notif.id }
|
||||
navigation = { navigation } />
|
||||
default:
|
||||
// We're not expecting polls to be super popular on Pixelfed
|
||||
return <></>
|
||||
}
|
||||
}
|
||||
|
||||
const UserText = (props) => {
|
||||
return (
|
||||
<Text
|
||||
style = { styles.bold }
|
||||
onPress = {
|
||||
() => {
|
||||
props.navigation.navigate("ViewProfile", {
|
||||
acct: props.acct
|
||||
});
|
||||
}
|
||||
}>
|
||||
{ props.acct }
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
|
||||
const Notification = (props) => {
|
||||
return (
|
||||
<View style = { styles.notif.container }>
|
||||
<View style = { styles.notif.thumbnailContainer }>
|
||||
<TouchableOpacity
|
||||
onPress = { props.thumbnailPressCallback }>
|
||||
<Image
|
||||
style = {
|
||||
[
|
||||
styles.notif.thumbnail,
|
||||
props.thumbnailStyles
|
||||
]
|
||||
}
|
||||
source = { { uri: props.thumbnail } } />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<View style = { styles.notif.contentContainer }>
|
||||
{ props.children }
|
||||
</View>
|
||||
{ props.button ?
|
||||
<View style = { styles.notif.buttonContainer }>
|
||||
<TouchableOpacity
|
||||
style = { styles.notif.button }
|
||||
onPress = { props.buttonCallback }>
|
||||
<Text>{ props.buttonLabel }</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
: <></>
|
||||
}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const Follow = (props) => {
|
||||
return (
|
||||
<Notification
|
||||
thumbnail = { props.data.account.avatar }
|
||||
thumbnailStyles = { styles.notif.circularThumbnail }
|
||||
thumbnailPressCallback = {
|
||||
navigateProfileFactory(props.navigation, props.data.account.acct)
|
||||
}>
|
||||
<Text style = { styles.notif.content }>
|
||||
<UserText acct = { props.data.account.acct } />
|
||||
has followed you.
|
||||
</Text>
|
||||
</Notification>
|
||||
);
|
||||
};
|
||||
|
||||
const FollowRequest = (props) => {
|
||||
return (
|
||||
<Notification
|
||||
thumbnail = { props.data.account.avatar }
|
||||
thumbnailStyles = { styles.notif.circularThumbnail }
|
||||
thumbnailPressCallback = {
|
||||
navigateProfileFactory(props.navigation, props.data.account.acct)
|
||||
}
|
||||
button = { true }
|
||||
buttonLabel = { "Accept" }
|
||||
buttonCallback = { () => console.log("Request accepted") }>
|
||||
<Text style = { styles.notif.content }>
|
||||
<UserText acct = { props.data.account.acct } />
|
||||
has requested to follow you.
|
||||
</Text>
|
||||
</Notification>
|
||||
);
|
||||
};
|
||||
|
||||
const Mention = (props) => {
|
||||
let uri;
|
||||
let imageStyle;
|
||||
let thumbnailCallback;
|
||||
|
||||
if (props.data.status.media_attachments.length > 0) {
|
||||
// If it's a comment...
|
||||
uri = props.data.status.media_attachments[0].url;
|
||||
imageStyle = {};
|
||||
thumbnailCallback = navigatePostFactory(
|
||||
props.navigation,
|
||||
props.data.status.id
|
||||
);
|
||||
} else {
|
||||
// If it's a reply to your comment...
|
||||
uri = props.data.account.avatar;
|
||||
imageStyle = styles.notif.circularThumbnail;
|
||||
thumbnailCallback = navigateProfileFactory(
|
||||
props.navigation,
|
||||
props.data.account.acct
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Notification
|
||||
thumbnail = { uri }
|
||||
thumbnailStyles = { imageStyle }i
|
||||
thumbnailPressCallback = { thumbnailCallback }>
|
||||
<Text style = { styles.notif.content }>
|
||||
<UserText acct = { props.data.account.acct } />
|
||||
mentioned you:
|
||||
<Text style = { styles.notif.status }>
|
||||
"{ props.data.status.content }"
|
||||
</Text>
|
||||
</Text>
|
||||
</Notification>
|
||||
);
|
||||
};
|
||||
|
||||
const Reblog = (props) => {
|
||||
return (
|
||||
<Notification
|
||||
thumbnail = { props.data.status.media_attachments[0].url }
|
||||
thumbnailPressCallback = {
|
||||
navigatePostFactory(props.navigation, props.data.status.id)
|
||||
}>
|
||||
<FontAwesome
|
||||
name = "retweet"
|
||||
color = "#000"
|
||||
size = { 20 }
|
||||
style = { styles.notif.inlineIcon }/>
|
||||
<Text style = { styles.notif.content }>
|
||||
<UserText acct = { props.data.account.acct } />
|
||||
shared your post.
|
||||
</Text>
|
||||
</Notification>
|
||||
);
|
||||
};
|
||||
|
||||
const Favourite = (props) => {
|
||||
return (
|
||||
<Notification
|
||||
thumbnail = { props.data.status.media_attachments[0].url }
|
||||
thumbnailPressCallback = {
|
||||
navigatePostFactory(props.navigation, props.data.status.id)
|
||||
}>
|
||||
<FontAwesome
|
||||
name = "heart"
|
||||
size = { 20 }
|
||||
color = "black"
|
||||
style = { styles.notif.inlineIcon }/>
|
||||
<Text style = { styles.notif.content }>
|
||||
<UserText acct = { props.data.account.acct } />
|
||||
liked your post.
|
||||
</Text>
|
||||
</Notification>
|
||||
);
|
||||
};
|
||||
|
||||
const Status = (props) => {
|
||||
return (
|
||||
<Notification
|
||||
thumbnail = { props.data.status.media_attachments[0].url }
|
||||
thumbnailPressCallback = {
|
||||
navigatePostFactory(props.navigation, props.data.status.id)
|
||||
}>
|
||||
<Text style = { styles.notif.content }>
|
||||
<UserText acct = { props.data.account.acct } />
|
||||
just posted.
|
||||
</Text>
|
||||
</Notification>
|
||||
);
|
||||
};
|
||||
|
||||
const Notifications = ({navigation}) => {
|
||||
const [state, setState] = useState({
|
||||
loaded: false,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const read = JSON.stringify({
|
||||
unread: false,
|
||||
memory: [
|
||||
{ id: 1 },
|
||||
{ id: 2 },
|
||||
{ id: 3 },
|
||||
]
|
||||
});
|
||||
|
||||
AsyncStorage.mergeItem("@user_notifications", read)
|
||||
.then(() => {
|
||||
setState({...state,
|
||||
notifications: TEST_NOTIFICATIONS,
|
||||
loaded: true
|
||||
})
|
||||
});
|
||||
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
navigation = { navigation }>
|
||||
{ state.loaded ?
|
||||
<View>
|
||||
{
|
||||
state.notifications.map(notif =>
|
||||
renderNotification(notif, navigation)
|
||||
)
|
||||
}
|
||||
</View>
|
||||
: <></>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const SCREEN_WIDTH = Dimensions.get("window").width;
|
||||
|
||||
const styles = {
|
||||
notif: {
|
||||
container: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
paddingLeft: 20,
|
||||
marginTop: 10,
|
||||
marginBottom: 10,
|
||||
},
|
||||
|
||||
circularThumbnail: { borderRadius: SCREEN_WIDTH / 16 },
|
||||
thumbnailContainer: {
|
||||
marginRight: 10,
|
||||
},
|
||||
thumbnail: {
|
||||
width: SCREEN_WIDTH / 8,
|
||||
height: SCREEN_WIDTH / 8,
|
||||
},
|
||||
|
||||
contentContainer: {
|
||||
flexShrink: 1,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
},
|
||||
inlineIcon: {
|
||||
marginRight: 10,
|
||||
},
|
||||
status: { fontStyle: "italic" },
|
||||
|
||||
buttonContainer: {
|
||||
marginLeft: "auto",
|
||||
marginRight: 10,
|
||||
},
|
||||
button: {
|
||||
borderWidth: 1,
|
||||
borderColor: "#888",
|
||||
borderRadius: 10,
|
||||
padding: 10,
|
||||
},
|
||||
},
|
||||
bold: { fontWeight: "bold" },
|
||||
};
|
||||
|
||||
export default Notifications;
|
|
@ -1,6 +1,7 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
|
||||
import {
|
||||
ScrollView,
|
||||
SafeAreaView,
|
||||
View,
|
||||
TextInput,
|
||||
|
@ -9,11 +10,11 @@ import {
|
|||
TouchableOpacity,
|
||||
Dimensions,
|
||||
} from "react-native";
|
||||
import { FontAwesome } from '@expo/vector-icons';
|
||||
import mime from "mime";
|
||||
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
import * as requests from "src/requests";
|
||||
import Icon from "src/components/icons.js";
|
||||
|
||||
import * as ImagePicker from 'expo-image-picker';
|
||||
|
||||
|
@ -36,7 +37,6 @@ const Settings = (props) => {
|
|||
|
||||
await AsyncStorage.multiRemove([
|
||||
"@user_profile",
|
||||
"@user_notifications",
|
||||
"@user_instance",
|
||||
"@user_token",
|
||||
]);
|
||||
|
@ -134,7 +134,7 @@ const Settings = (props) => {
|
|||
return (
|
||||
<>
|
||||
{ state.loaded
|
||||
? <>
|
||||
? <ScrollView>
|
||||
<View style = { styles.avatar.container }>
|
||||
<Image
|
||||
source = { { uri: state.newAvatar.uri } }
|
||||
|
@ -190,8 +190,8 @@ const Settings = (props) => {
|
|||
<View style = { styles.check.container }>
|
||||
<>
|
||||
{ !state.locked
|
||||
? <FontAwesome name="square-o" size={24} color="black" />
|
||||
: <FontAwesome name="check-square-o" size={24} color="black" />
|
||||
? <Icon name="square" size={24}/>
|
||||
: <Icon name="checkbox" size={24}/>
|
||||
}
|
||||
</>
|
||||
<Text style = { styles.check.label }>
|
||||
|
@ -215,7 +215,7 @@ const Settings = (props) => {
|
|||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</>
|
||||
</ScrollView>
|
||||
: <></>
|
||||
}
|
||||
</>
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
ScrollView,
|
||||
Dimensions,
|
||||
View,
|
||||
Image,
|
||||
Text,
|
||||
TextInput,
|
||||
} from "react-native";
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
|
||||
import { getAutoHeight } from "src/interface/rendering";
|
||||
import { TouchableOpacity } from "react-native-gesture-handler";
|
||||
|
@ -15,6 +15,7 @@ import mime from "mime";
|
|||
import * as ImagePicker from 'expo-image-picker';
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
import * as requests from "src/requests";
|
||||
import Icon from "src/components/icons.js";
|
||||
|
||||
const Publish = ({ navigation }) => {
|
||||
const [ state, setState ] = useState({
|
||||
|
@ -104,7 +105,7 @@ const Publish = ({ navigation }) => {
|
|||
};
|
||||
|
||||
const Selector = (props) => {
|
||||
const color = props.active == props.visibility ? "black" : "#888";
|
||||
const color = props.active == props.visibility ? "black" : "#666";
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
|
@ -113,9 +114,9 @@ const Publish = ({ navigation }) => {
|
|||
() => setState({ ...state, visibility: props.visibility })
|
||||
}>
|
||||
<View style = { styles.form.option.inner }>
|
||||
<Ionicons
|
||||
<Icon
|
||||
name = { props.icon }
|
||||
color = { color }
|
||||
focused = { props.active }
|
||||
size={24} />
|
||||
<Text style = { { color } }>
|
||||
|
||||
|
@ -126,62 +127,57 @@ const Publish = ({ navigation }) => {
|
|||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{ state.loaded
|
||||
? <>
|
||||
<View style = { styles.preview.container }>
|
||||
<Image
|
||||
style = {[
|
||||
styles.preview.image,
|
||||
{
|
||||
width: state.image.width,
|
||||
height: state.image.height,
|
||||
},
|
||||
]}
|
||||
source = { { uri: state.image.data.uri } } />
|
||||
</View>
|
||||
<View style = { styles.form.container }>
|
||||
<TextInput
|
||||
placeholder = "Caption this post..."
|
||||
value = { state.caption }
|
||||
multiline
|
||||
autoFocus
|
||||
onChangeText = {
|
||||
caption => setState({ ...state, caption })
|
||||
}
|
||||
style = { [ styles.form.input, { height: 100, } ] } />
|
||||
return state.loaded
|
||||
? <ScrollView>
|
||||
<View style = { styles.preview.container }>
|
||||
<Image
|
||||
style = {[
|
||||
styles.preview.image,
|
||||
{
|
||||
width: state.image.width,
|
||||
height: state.image.height,
|
||||
},
|
||||
]}
|
||||
source = { { uri: state.image.data.uri } } />
|
||||
</View>
|
||||
<View style = { styles.form.container }>
|
||||
<TextInput
|
||||
placeholder = "Caption this post..."
|
||||
value = { state.caption }
|
||||
multiline
|
||||
autoFocus
|
||||
onChangeText = {
|
||||
caption => setState({ ...state, caption })
|
||||
}
|
||||
style = { [ styles.form.input, { height: 100, } ] } />
|
||||
|
||||
<Text style = { styles.form.label }>Visibility</Text>
|
||||
<Selector
|
||||
visibility = "public"
|
||||
active = { state.visibility }
|
||||
icon = "globe-outline"
|
||||
message = "Anyone can see this post" />
|
||||
<Selector
|
||||
visibility = "unlisted"
|
||||
active = { state.visibility }
|
||||
icon = "lock-open-outline"
|
||||
message = "Keep this post off public timelines" />
|
||||
<Selector
|
||||
visibility = "private"
|
||||
active = { state.visibility }
|
||||
icon = "lock-closed-outline"
|
||||
message = "Only share this with my followers" />
|
||||
<Text style = { styles.form.label }>Visibility</Text>
|
||||
<Selector
|
||||
visibility = "public"
|
||||
active = { state.visibility }
|
||||
icon = "planet"
|
||||
message = "Anyone can see this post" />
|
||||
<Selector
|
||||
visibility = "unlisted"
|
||||
active = { state.visibility }
|
||||
icon = "lock-open"
|
||||
message = "Keep this post off public timelines" />
|
||||
<Selector
|
||||
visibility = "private"
|
||||
active = { state.visibility }
|
||||
icon = "lock-closed"
|
||||
message = "Only share this with my followers" />
|
||||
|
||||
<TouchableOpacity
|
||||
onPress = { _handlePublish }
|
||||
style = { styles.form.button.container }>
|
||||
<Text style = { styles.form.button.label }>
|
||||
Publish
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</>
|
||||
: <></>
|
||||
}
|
||||
</>
|
||||
);
|
||||
<TouchableOpacity
|
||||
onPress = { _handlePublish }
|
||||
style = { styles.form.button.container }>
|
||||
<Text style = { styles.form.button.label }>
|
||||
Publish
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</ScrollView>
|
||||
: <></>;
|
||||
};
|
||||
|
||||
const SCREEN_WIDTH = Dimensions.get("window").width;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import React from "react";
|
||||
import {
|
||||
ScrollView,
|
||||
SafeAreaView,
|
||||
View,
|
||||
Image,
|
||||
|
@ -10,12 +11,12 @@ import {
|
|||
|
||||
import ModerateMenu from "src/components/moderate-menu.js";
|
||||
|
||||
const UserList = ({navigation}) => {
|
||||
const data = navigation.getParam("data", [])
|
||||
const context = navigation.getParam("context", "");
|
||||
const UserList = ({ navigation, route}) => {
|
||||
const data = route.params.data;
|
||||
const context = route.params.context;
|
||||
|
||||
return (
|
||||
<>
|
||||
<ScrollView>
|
||||
{
|
||||
context ?
|
||||
<Text style = { styles.context }>
|
||||
|
@ -55,7 +56,7 @@ const UserList = ({navigation}) => {
|
|||
</View>
|
||||
)
|
||||
}
|
||||
</>
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ import {
|
|||
TextInput,
|
||||
Text
|
||||
} from "react-native";
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { ScrollView } from "react-native-gesture-handler";
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
|
||||
|
@ -17,6 +16,7 @@ import {
|
|||
timeToAge,
|
||||
StatusBarSpace
|
||||
} from "src/interface/rendering";
|
||||
import Icon from "src/components/icons.js";
|
||||
import { activeOrNot } from "src/interface/interactions";
|
||||
|
||||
import TimelineView from "src/components/posts/timeline-view";
|
||||
|
@ -169,7 +169,7 @@ const Comment = (props) => {
|
|||
{
|
||||
timeToAge(
|
||||
Date.now(),
|
||||
(new Date(props.data.created_at)).getTime()
|
||||
(new Date(proIconcreated_at)).getTime()
|
||||
)
|
||||
}
|
||||
</Text>
|
||||
|
@ -189,18 +189,15 @@ const Comment = (props) => {
|
|||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
onPress = { props.onFavouriteFactory(props.data) }>
|
||||
<Ionicons
|
||||
name = { activeOrNot(props.data.favourited, packs.favourited) }
|
||||
size = { 15 }
|
||||
style = { styles.action }/>
|
||||
<Icon
|
||||
name = { "heart" }
|
||||
focused = { props.data.favourited }
|
||||
size = { 15 }/>
|
||||
</TouchableOpacity>
|
||||
<View style = { { paddingLeft: 10, } }>
|
||||
<Menu renderer = { SlideInMenu }>
|
||||
<MenuTrigger>
|
||||
<Ionicons
|
||||
name="ellipsis-horizontal"
|
||||
size={18}
|
||||
color="#666" />
|
||||
<Icon name="ellipsis" size={18}/>
|
||||
</MenuTrigger>
|
||||
<MenuOptions customStyles = { menuOptionsStyles }>
|
||||
{ props.profile.acct == props.data.account.acct
|
||||
|
@ -444,7 +441,7 @@ const ViewComments = (props) => {
|
|||
{ state.inReplyTo.id != postData.id
|
||||
? <TouchableOpacity onPress = { _handleCancelSubReply }>
|
||||
<View style = { styles.form.inReplyTo.container }>
|
||||
<Ionicons name="close" size={24} color="#666" />
|
||||
<Icon name="close" size={24}/>
|
||||
<Text style = { styles.form.inReplyTo.message }>
|
||||
Replying to
|
||||
<Text style = { styles.bold }>
|
||||
|
@ -468,10 +465,7 @@ const ViewComments = (props) => {
|
|||
onChangeText = { c => setState({...state, reply: c }) }/>
|
||||
<View style = { styles.submitContainer }>
|
||||
<TouchableOpacity onPress = { _handleSubmitReply }>
|
||||
<Ionicons
|
||||
name = "paper-plane-outline"
|
||||
color = "black"
|
||||
size = { 30 }/>
|
||||
<Icon name="paper-plane" size={30}/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
|
|
|
@ -40,7 +40,7 @@ const GridView = (props) => {
|
|||
&& p.media_attachments.length > 0
|
||||
);
|
||||
|
||||
let rows = partition(props.posts, 3);
|
||||
let rows = partition(postsWithMedia, 3);
|
||||
return (
|
||||
<View>
|
||||
{
|
||||
|
|
|
@ -6,8 +6,6 @@ import {
|
|||
Dimensions,
|
||||
TouchableOpacity
|
||||
} from "react-native";
|
||||
import { FontAwesome } from "@expo/vector-icons";
|
||||
import { activeOrNot } from "src/interface/interactions";
|
||||
|
||||
const PostAction = (props) => {
|
||||
return (
|
||||
|
@ -15,48 +13,29 @@ const PostAction = (props) => {
|
|||
onPress = { props.onPress }>
|
||||
<View style = { { marginLeft: SCREEN_WIDTH / 20 } }>
|
||||
<FontAwesome
|
||||
name = { activeOrNot(props.active, props.pack) }
|
||||
name = { props.icon }
|
||||
size = { 24 }
|
||||
color = {
|
||||
activeOrNot(props.active, {
|
||||
active: "#000",
|
||||
inactive: "#888",
|
||||
})
|
||||
}/>
|
||||
focused = { props.active }/>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
)
|
||||
}
|
||||
|
||||
const PostActionBar = (props) => {
|
||||
const icons = {
|
||||
heart: {
|
||||
active: "heart",
|
||||
inactive: "heart-o",
|
||||
},
|
||||
reblog: {
|
||||
active: "retweet",
|
||||
inactive: "retweet",
|
||||
},
|
||||
bookmark: {
|
||||
active: "bookmark",
|
||||
inactive: "bookmark-o",
|
||||
}
|
||||
}
|
||||
return (
|
||||
<View style = { styles.flexContainer }>
|
||||
<PostAction
|
||||
pack = { icons.heart }
|
||||
icon = { "heart" }
|
||||
active = { props.favourited }
|
||||
onPress = { props.onFavourite } />
|
||||
|
||||
<PostAction
|
||||
pack = { icons.reblog }
|
||||
icon = { "boost" }
|
||||
active = { props.reblogged }
|
||||
onPress = { props.onReblog }/>
|
||||
|
||||
<PostAction
|
||||
pack = { icons.bookmark }
|
||||
icon = { "bookmark" }
|
||||
active = { props.bookmarked }
|
||||
onPress = { props.onBookmark } />
|
||||
</View>
|
||||
|
|
|
@ -80,6 +80,12 @@ export const RawPost = (props) => {
|
|||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (props.onRendered != null) {
|
||||
props.onRendered();
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<View>
|
||||
<View style = { styles.postHeader }>
|
||||
|
@ -214,13 +220,14 @@ export const PostByData = (props) => {
|
|||
own,
|
||||
loaded: true
|
||||
});
|
||||
|
||||
if (props.onPostLoaded != null) {
|
||||
props.onPostLoaded();
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// This is run after the state has been updated
|
||||
props.onPostLoaded();
|
||||
}, [state])
|
||||
|
||||
const _handleFavourite = async () => {
|
||||
let newStatus;
|
||||
|
||||
|
@ -349,6 +356,7 @@ export const PostByData = (props) => {
|
|||
<RawPost
|
||||
data = { state.data }
|
||||
dimensions = { state.dimensions }
|
||||
onRendered = { props.onRendered }
|
||||
onFavourite = { _handleFavourite }
|
||||
onReblog = { _handleReblog }
|
||||
onBookmark = { _handleDelete }
|
||||
|
|
|
@ -23,7 +23,9 @@ const TimelineView = (props) => {
|
|||
}
|
||||
}, [postsLoaded]);
|
||||
|
||||
_handlePostLoaded = () => setPostsLoaded(postsLoaded + 1);
|
||||
const _handlePostLoaded = () => {
|
||||
setPostsLoaded(postsLoaded + 1);
|
||||
}
|
||||
|
||||
return (
|
||||
<View>
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
|
||||
const TEST_NOTIFICATIONS = [{ id: 1 }, { id: 2 }];
|
||||
const TEST_NEW_NOTIFICATIONS_1 = [{ id: 1 }, { id: 2 }];
|
||||
const TEST_NEW_NOTIFICATIONS_2 = [{ id: 1 }, { id: 2 }, { id: 3 }];
|
||||
|
||||
export function objectToForm(obj) {
|
||||
let form = new FormData();
|
||||
|
||||
|
@ -14,31 +10,6 @@ export function objectToForm(obj) {
|
|||
return form;
|
||||
}
|
||||
|
||||
export async function checkUnreadNotifications() {
|
||||
// If the check has already been made since the last time notifications.js
|
||||
// has been opened
|
||||
const notifications = JSON.parse(await AsyncStorage.getItem("@user_notifications"));
|
||||
|
||||
if (notifications.unread) {
|
||||
return true;
|
||||
} else {
|
||||
// Some promise to get new notifications
|
||||
const newNotifs = await Promise.resolve(TEST_NEW_NOTIFICATIONS_2);
|
||||
|
||||
const isUnread = JSON.stringify(newNotifs) != JSON.stringify(notifications.memory);
|
||||
|
||||
// Update stored notifications
|
||||
await AsyncStorage.setItem(
|
||||
"@user_notifications",
|
||||
JSON.stringify({...notifications,
|
||||
unread: isUnread,
|
||||
})
|
||||
);
|
||||
|
||||
return isUnread;
|
||||
}
|
||||
}
|
||||
|
||||
export async function postForm(url, data = false, token = false, contentType = false) {
|
||||
// Send a POST request with data formatted with FormData returning JSON
|
||||
let headers = {};
|
||||
|
@ -66,7 +37,7 @@ export async function post(url, token = false) {
|
|||
return resp;
|
||||
}
|
||||
|
||||
export async function get(url, token = false, data = false) {
|
||||
export async function get(url, token , data = false) {
|
||||
let completeURL;
|
||||
if (data) {
|
||||
let params = new URLSearchParams(data);
|
||||
|
@ -85,7 +56,7 @@ export async function get(url, token = false, data = false) {
|
|||
return resp;
|
||||
}
|
||||
|
||||
export async function _delete(url, token = false) {
|
||||
export async function _delete(url, token) {
|
||||
const resp = await fetch(url, {
|
||||
method: "DELETE",
|
||||
headers: token
|
||||
|
@ -104,8 +75,8 @@ export async function verifyCredentials(domain, token) {
|
|||
return resp.json();
|
||||
}
|
||||
|
||||
export async function fetchProfile(domain, id) {
|
||||
const resp = await get(`https://${domain}/api/v1/accounts/${id}`);
|
||||
export async function fetchProfile(domain, id, token) {
|
||||
const resp = await get(`https://${domain}/api/v1/accounts/${id}`, token);
|
||||
return resp.json();
|
||||
}
|
||||
|
||||
|
|