Improve loading of profile data

This commit is contained in:
Nat 2021-05-04 18:13:17 -03:00
parent 31d8282bdc
commit 856f215e36
3 changed files with 268 additions and 173 deletions

View File

@ -14,33 +14,7 @@ import * as Linking from "expo-linking";
import * as WebBrowser from "expo-web-browser"; import * as WebBrowser from "expo-web-browser";
import Constants from "expo-constants"; import Constants from "expo-constants";
async function postForm(url, data, token = false) { import * as requests from "src/requests";
// Send a POST request with data formatted with FormData returning JSON
let form = new FormData();
for (let key in data) {
form.append(key, data[key]);
}
const response = await fetch(url, {
method: "POST",
body: form,
headers: token
? { "Authorization": `Bearer ${token}`, }
: {},
});
return response.json();
}
async function get(url, token = false) {
const response = await fetch(url, {
method: "GET",
headers: token
? { "Authorization": `Bearer ${token}`, }
: {},
});
return response.json();
}
const AuthenticateJsx = ({navigation}) => { const AuthenticateJsx = ({navigation}) => {
const REDIRECT_URI = Linking.makeUrl("authenticate"); const REDIRECT_URI = Linking.makeUrl("authenticate");
@ -76,15 +50,17 @@ const AuthenticateJsx = ({navigation}) => {
scope: "read write follow push", scope: "read write follow push",
}; };
const token = await postForm(`${api}/oauth/token`, tokenRequestBody); const token = await requests
.postForm(`${api}/oauth/token`, tokenRequestBody)
.then(resp => resp.json());
// Store the token // Store the token
AsyncStorage.setItem("@user_token", JSON.stringify(token)); AsyncStorage.setItem("@user_token", JSON.stringify(token));
const profile = await get( const profile = await requests.get(
`${api}/api/v1/accounts/verify_credentials`, `${api}/api/v1/accounts/verify_credentials`,
token.access_token token.access_token
); ).then(resp => resp.json());
await AsyncStorage.multiSet([ await AsyncStorage.multiSet([
[ "@user_profile", JSON.stringify(profile), ], [ "@user_profile", JSON.stringify(profile), ],
@ -122,12 +98,12 @@ const AuthenticateJsx = ({navigation}) => {
// Ensure the app has been created // Ensure the app has been created
if (appJSON == null) { if (appJSON == null) {
// Register app: https://docs.joinmastodon.org/methods/apps/#create-an-application // Register app: https://docs.joinmastodon.org/methods/apps/#create-an-application
app = await postForm(`${url}/api/v1/apps`, { app = await requests.postForm(`${url}/api/v1/apps`, {
client_name: "Resin", client_name: "Resin",
redirect_uris: REDIRECT_URI, redirect_uris: REDIRECT_URI,
scopes: "read write follow push", scopes: "read write follow push",
website: "https://github.com/natjms/resin", website: "https://github.com/natjms/resin",
}); }).then(resp => resp.json());
await AsyncStorage await AsyncStorage
.setItem("@app_object", JSON.stringify(app)) .setItem("@app_object", JSON.stringify(app))

View File

@ -13,6 +13,7 @@ import AsyncStorage from "@react-native-async-storage/async-storage";
import { activeOrNot } from "src/interface/interactions"; import { activeOrNot } from "src/interface/interactions";
import { withoutHTML } from "src/interface/rendering"; import { withoutHTML } from "src/interface/rendering";
import * as requests from "src/requests";
import GridViewJsx from "src/components/posts/grid-view"; import GridViewJsx from "src/components/posts/grid-view";
import { import {
@ -89,13 +90,15 @@ const TEST_THEIR_FOLLOWERS = [
{ id: 6 }, { id: 6 },
]; ];
function getMutuals(yours, theirs) { function getMutuals(yourFollowing, theirFollowers) {
// Where yours and theirs are arrays of followers, as returned by the oAPI // Where yours and theirs are arrays of followers, as returned by the API
// Returns a list of people you are following that are following some other
// account
const idify = ({id}) => id; const acctsArray = ({acct}) => acct;
const asIDs = new Set(theirs.map(idify)); const asIDs = new Set(theirFollowers.map(acctArray));
return yours.filter(x => asIDs.has(idify(x))); return yourFollowing.filter(x => asIDs.has(idify(x)));
} }
const HTMLLink = ({link}) => { const HTMLLink = ({link}) => {
@ -118,62 +121,143 @@ const HTMLLink = ({link}) => {
} }
} }
const ProfileJsx = ({navigation}) => {
return (
<ScreenWithTrayJsx
active = "Profile"
navigation = { navigation }
active = "Profile">
<ProfileDisplayJsx navigation = { navigation }/>
</ScreenWithTrayJsx>
);
};
const ViewProfileJsx = ({navigation}) => { const ViewProfileJsx = ({navigation}) => {
// As rendered when opened from somewhere other than the tab bar
const [state, setState] = useState({
loaded: false,
profile: navigation.getParam("profile"),
});
useEffect(() => {
AsyncStorage
.multiGet(["@user_profile", "@user_instance", "@user_token"])
.then(([ownProfilePair, ownDomainPair, tokenPair]) =>
[
JSON.parse(ownProfilePair[1]),
ownDomainPair[1],
JSON.parse(tokenPair[1]).access_token,
]
)
.then(([ ownProfile, ownDomain, accessToken ]) => {
const parsedAcct = state.profile.acct.split("@");
const domain = parsedAcct.length == 1
? ownDomain // There's no @ in the acct, thus it's a local user
: parsedAcct [1] // The part of profile.acct after the @
return Promise.all([
requests.fetchFollowing(
ownDomain,
ownProfile.id,
accessToken
),
requests.fetchFollowers(
domain,
state.profile.id,
accessToken
),
]);
})
.then(([ ownFollowing, theirFollowers ]) =>
setState({...state,
mutuals: getMutuals(ownFollowing, theirFollowers),
loaded: true,
})
);
}, []);
return ( return (
<ScreenWithFullNavigationJsx <>
{ state.loaded
? <ScreenWithFullNavigationJsx
active = { navigation.getParam("originTab") } active = { navigation.getParam("originTab") }
navigation = { navigation }> navigation = { navigation }>
<ProfileDisplayJsx navigation = { navigation } /> <RawProfileJsx
profile = { state.profile }
notifs = { state.notifs }
posts = { TEST_POSTS }/>
</ScreenWithFullNavigationJsx> </ScreenWithFullNavigationJsx>
: <></>
}
</>
); );
} }
const ProfileDisplayJsx = ({navigation}) => { const ProfileJsx = ({ navigation }) => {
const accountName = navigation.getParam("acct", ""); const [state, setState] = useState({
let [state, setState] = useState({
loaded: false, loaded: false,
}); });
useEffect(() => {
let profile;
let notifs;
let domain;
let accessToken;
AsyncStorage
.multiGet([
"@user_profile",
"@user_notifications",
"@user_instance",
])
.then(([profilePair, notifPair, domainPair]) => {
profile = JSON.parse(profilePair[1]);
notifs = JSON.parse(notifPair[1]);
domain = domainPair[1];
return requests.fetchProfile(domain, profile.id);
})
.then(latestProfile => {
if(JSON.stringify(latestProfile) != JSON.stringify(profile)) {
profile = latestProfile
}
setState({...state,
profile: profile,
notifs: notifs,
loaded: true,
});
});
}, []);
return (
<>
{ state.loaded
? <ScreenWithTrayJsx
active = "Profile"
navigation = { navigation }
active = "Profile">
<RawProfileJsx
navigation = { navigation }
own = { true }
profile = { state.profile }
posts = { TEST_POSTS }
notifs = { state.notifs }/>
</ScreenWithTrayJsx>
: <></>
}
</>
)
};
const RawProfileJsx = (props) => {
let [state, setState] = useState({
own: props.own,
profile: props.profile,
notifs: props.notifs,
});
const notif_pack = { const notif_pack = {
active: require("assets/eva-icons/bell-unread.png"), active: require("assets/eva-icons/bell-unread.png"),
inactive: require("assets/eva-icons/bell-black.png") inactive: require("assets/eva-icons/bell-black.png")
} }
useEffect(() => { const _handleFollow = () => {};
AsyncStorage.multiGet(["@user_profile", "@user_notifications"])
.then(values => {
const [profileJSON, notificationsJSON] = values;
const profile = JSON.parse(profileJSON[1]);
const notifications = JSON.parse(notificationsJSON[1]);
setState({
profile: profile,
unreadNotifications: notifications.unread,
mutuals: getMutuals(TEST_YOUR_FOLLOWERS, TEST_THEIR_FOLLOWERS),
own: true,
loaded: true,
});
});
}, []);
let profileButton; let profileButton;
if (state.own) { if (props.own) {
profileButton = ( profileButton = (
<TouchableOpacity <TouchableOpacity
onPress = { onPress = {
() => { () => {
navigation.navigate("Settings"); props.navigation.navigate("Settings");
} }
}> }>
<View style = { styles.button }> <View style = { styles.button }>
@ -183,7 +267,7 @@ const ProfileDisplayJsx = ({navigation}) => {
); );
} else { } else {
profileButton = ( profileButton = (
<TouchableOpacity> <TouchableOpacity onPress = { _handleFollow }>
<View style = { styles.button }> <View style = { styles.button }>
<Text style = { styles.buttonText }>Follow</Text> <Text style = { styles.buttonText }>Follow</Text>
</View> </View>
@ -193,20 +277,18 @@ const ProfileDisplayJsx = ({navigation}) => {
return ( return (
<View> <View>
{ state.loaded ?
<>
<View style = { styles.jumbotron }> <View style = { styles.jumbotron }>
<View style = { styles.profileHeader }> <View style = { styles.profileHeader }>
<Image <Image
source = { { uri: state.profile.avatar } } source = { { uri: props.profile.avatar } }
style = { styles.avatar } /> style = { styles.avatar } />
<View> <View>
<Text <Text
style = { styles.displayName }> style = { styles.displayName }>
{state.profile.display_name} { props.profile.display_name}
</Text> </Text>
<Text style={ styles.strong }> <Text style={ styles.strong }>
@{state.profile.username } @{ props.profile.acct }
</Text> </Text>
</View> </View>
{ {
@ -215,13 +297,13 @@ const ProfileDisplayJsx = ({navigation}) => {
<TouchableOpacity <TouchableOpacity
onPress = { onPress = {
() => { () => {
navigation.navigate("Notifications"); props.navigation.navigate("Notifications");
} }
}> }>
<Image <Image
source = { source = {
activeOrNot( activeOrNot(
state.unreadNotifications, props.notifs.unread,
notif_pack notif_pack
) )
} }
@ -234,32 +316,31 @@ const ProfileDisplayJsx = ({navigation}) => {
} }
</View> </View>
<Text style = { styles.accountStats }> <Text style = { styles.accountStats }>
{ state.profile.statuses_count } posts &#8226;&nbsp; { props.profile.statuses_count } posts &#8226;&nbsp;
<Text onPress = { <Text onPress = {
() => { () => {
const context = state.own ? const context = props.own ?
"People following you" "People following you"
: "Your mutual followers with " + state.profile.display_name; : "Your mutual followers with " + props.profile.display_name;
navigation.navigate("UserList", { props.navigation.navigate("UserList", {
data: [/*Some array of users*/], context: context,
context: context
}); });
} }
}> }>
{ {
state.own ? state.own ?
<>View followers</> <>View followers</>
: <>{state.mutuals.length + " mutuals"}</> : <>{ props.mutuals + " mutuals" }</>
} }
</Text> </Text>
</Text> </Text>
<Text style = { styles.note }> <Text style = { styles.note }>
{state.profile.note} {props.profile.note}
</Text> </Text>
<View style = { styles.fields.container }> <View style = { styles.fields.container }>
{ state.profile.fields { props.profile.fields
? state.profile.fields.map((field, index) => ( ? props.profile.fields.map((field, index) => (
<View <View
style = { styles.fields.row } style = { styles.fields.row }
key = { index }> key = { index }>
@ -282,18 +363,15 @@ const ProfileDisplayJsx = ({navigation}) => {
</View> </View>
<GridViewJsx <GridViewJsx
posts = { TEST_POSTS } posts = { props.posts }
openPostCallback = { openPostCallback = {
(id) => { (id) => {
navigation.navigate("ViewPost", { props.navigation.navigate("ViewPost", {
id: id, id: id,
originTab: "Profile" originTab: "Profile"
}); });
} }
} /> } />
</>
: <View></View>
}
</View> </View>
); );
}; };
@ -371,5 +449,4 @@ const styles = {
}, },
}; };
export { ViewProfileJsx, ProfileDisplayJsx }; export { ViewProfileJsx, ProfileJsx as default };
export default ProfileJsx;

View File

@ -28,3 +28,45 @@ export async function checkUnreadNotifications() {
return isUnread; return isUnread;
} }
} }
export async function postForm(url, data, token = false) {
// Send a POST request with data formatted with FormData returning JSON
let form = new FormData();
for (let key in data) {
form.append(key, data[key]);
}
const resp = await fetch(url, {
method: "POST",
body: form,
headers: token
? { "Authorization": `Bearer ${token}`, }
: {},
});
return resp;
}
export function get(url, token = false) {
return fetch(url, {
method: "GET",
headers: token
? { "Authorization": `Bearer ${token}`, }
: {},
});
}
export async function fetchProfile(domain, id) {
const resp = await get(`https://${domain}/api/v1/accounts/${id}`);
return resp.json();
}
export async function fetchFollowing(domain, id, token) {
const resp = await get(`https://${domain}/api/v1/accounts/${id}/following`, token);
return resp.json();
}
export async function fetchFollowers(domain, id, token) {
const resp = await get(`https://${domain}/api/v1/accounts/${id}/followers`, token);
return resp.json();
}