Compare commits

...

11 Commits

Author SHA1 Message Date
Nat 7c4ad599e8
Move all icons to the new framework 2023-01-02 10:13:38 -08:00
Nat c15d42f081
Introduce icons.js
The default export here replaces Expo's <Iconicons/>, <Fontawesome/>,
etc. components with <Icon/> in a first step away from the Expo
ecosystem.

Using PNG images greatly limits our access to unique colours, but since
there only should realistically be using two colours, #000 and #666, in
the interface, hopefully this won't be a problem. At least not until
there's a dark theme. Rendering more PNGs from the SVGs is tedious.
2023-01-01 16:52:07 -08:00
Nat db54b13fd2 Remove Notifications page from App.js 2022-05-30 17:06:48 -07:00
Nat 7f99ac8845 Remove notification features
Notifications will not be implemented in Resin for the foreseeable future
2022-05-30 15:12:12 -07:00
Nat eb9f047a04 Merge branch 'main' of https://github.com/natjms/resin into main 2022-05-30 14:58:26 -07:00
Nat 04fb46404f Ensure event actually fires when the timeline is loaded 2022-05-19 17:55:21 -07:00
Nat 68b6622cc2
Merge pull request #42 from smdpro/issue-41
profile page issue was fixed
2022-05-19 11:54:22 -07:00
Nat f889540ba5 Finish wrapping pages 2022-05-17 06:07:52 -07:00
Nat 00414df2a3 Fix centering issue with the timeline interruption 2022-05-16 17:53:02 -07:00
Nat 985cbe6ca1 Rewrap pages in ScrollViews 2022-05-16 17:10:06 -07:00
smadpro d408c139a9 [fix] profile issue 2022-05-16 03:53:46 +04:30
75 changed files with 3242 additions and 3488 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

1
assets/icons/boost.svg Normal file
View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

1
assets/icons/camera.svg Normal file
View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 680 B

1
assets/icons/close.svg Normal file
View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 959 B

1
assets/icons/create.svg Normal file
View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 546 B

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

1
assets/icons/feed.svg Normal file
View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

1
assets/icons/hashtag.svg Normal file
View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

1
assets/icons/heart.svg Normal file
View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 997 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1008 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

1
assets/icons/mail.svg Normal file
View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

1
assets/icons/person.svg Normal file
View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

1
assets/icons/planet.svg Normal file
View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

1
assets/icons/search.svg Normal file
View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 566 B

1
assets/icons/square.svg Normal file
View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

5569
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -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",

View File

@ -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>

View File

@ -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 }

103
src/components/icons.js Normal file
View File

@ -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;

View File

@ -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" />

View File

@ -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");
};

View File

@ -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>
: <></>
}
</>

View File

@ -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: "",

View File

@ -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>
? <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>
</View>
</TouchableWithoutFeedback>
<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: {
container: {
flexDirection: "row",
justifyContent: "center",
backgroundColor: "white",
padding: 20
padding: 20,
},
input: {
flexGrow: 1,
padding: 10,
fontSize: 17,
color: "#888"
},
submit: {
padding: 20,
}
},
searchBar: {
padding: 10,
fontSize: 17,

View File

@ -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>
: <></>
}
</>

View File

@ -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,6 +111,7 @@ const ViewHashtag = ({navigation}) => {
</View>
<>
{ state.loaded && state.posts.length > 0
? state.posts.length > 0
? <PagedGrid
navigation = { navigation }
posts = { state.posts }
@ -112,10 +119,11 @@ const ViewHashtag = ({navigation}) => {
: <Text style = { styles.nothing }>
Nothing to show
</Text>
: <></>
}
</>
</View>
</>
</ScrollView>
);
};

View File

@ -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,
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>
? <ScrollView contentContainerStyles = { styles.container }>
<TimelineView
navigation = { props.navigation }
posts = { state.posts }
onTimelineLoaded = { _handleTimelineLoaded }/>
{ endOfTimelineMessage }
</ScrollView>
: endOfTimelineMessage
: <></>
}
</>
@ -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,
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",
},
interruption: {
topBorder: {
borderTopWidth: 1,
borderTopColor: "#CCC",

View File

@ -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>
);
};

View File

@ -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;
AsyncStorage
.multiGet([
const init = async () => {
const [
profilePair,
instancePair,
tokenPair
] = await AsyncStorage.multiGet([
"@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;
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
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",
latestProfileString
);
}
setState({...state,
profile: profile,
notifs: notifs,
profile: latestProfile,
posts: posts,
listedUsers: followers,
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>
: <></>
}
</>

View File

@ -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 }&nbsp;
</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;

View File

@ -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>
: <></>
}
</>

View File

@ -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 } }>
&nbsp;
@ -126,10 +127,8 @@ const Publish = ({ navigation }) => {
);
};
return (
<>
{ state.loaded
? <>
return state.loaded
? <ScrollView>
<View style = { styles.preview.container }>
<Image
style = {[
@ -156,17 +155,17 @@ const Publish = ({ navigation }) => {
<Selector
visibility = "public"
active = { state.visibility }
icon = "globe-outline"
icon = "planet"
message = "Anyone can see this post" />
<Selector
visibility = "unlisted"
active = { state.visibility }
icon = "lock-open-outline"
icon = "lock-open"
message = "Keep this post off public timelines" />
<Selector
visibility = "private"
active = { state.visibility }
icon = "lock-closed-outline"
icon = "lock-closed"
message = "Only share this with my followers" />
<TouchableOpacity
@ -177,11 +176,8 @@ const Publish = ({ navigation }) => {
</Text>
</TouchableOpacity>
</View>
</>
: <></>
}
</>
);
</ScrollView>
: <></>;
};
const SCREEN_WIDTH = Dimensions.get("window").width;

View File

@ -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>
);
};

View File

@ -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 }>
&nbsp;Replying to&nbsp;
<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>

View File

@ -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>
{

View File

@ -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>

View File

@ -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 }

View File

@ -23,7 +23,9 @@ const TimelineView = (props) => {
}
}, [postsLoaded]);
_handlePostLoaded = () => setPostsLoaded(postsLoaded + 1);
const _handlePostLoaded = () => {
setPostsLoaded(postsLoaded + 1);
}
return (
<View>

View File

@ -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();
}