From 856f215e36b0bfedecb38a714a6afab0eb5ecfb6 Mon Sep 17 00:00:00 2001 From: natjms Date: Tue, 4 May 2021 18:13:17 -0300 Subject: [PATCH] Improve loading of profile data --- src/components/pages/authenticate.js | 40 +-- src/components/pages/profile.js | 359 ++++++++++++++++----------- src/requests.js | 42 ++++ 3 files changed, 268 insertions(+), 173 deletions(-) diff --git a/src/components/pages/authenticate.js b/src/components/pages/authenticate.js index 5a665cf..1d4817a 100644 --- a/src/components/pages/authenticate.js +++ b/src/components/pages/authenticate.js @@ -14,33 +14,7 @@ import * as Linking from "expo-linking"; import * as WebBrowser from "expo-web-browser"; import Constants from "expo-constants"; -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 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(); -} +import * as requests from "src/requests"; const AuthenticateJsx = ({navigation}) => { const REDIRECT_URI = Linking.makeUrl("authenticate"); @@ -76,15 +50,17 @@ const AuthenticateJsx = ({navigation}) => { 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 AsyncStorage.setItem("@user_token", JSON.stringify(token)); - const profile = await get( + const profile = await requests.get( `${api}/api/v1/accounts/verify_credentials`, token.access_token - ); + ).then(resp => resp.json()); await AsyncStorage.multiSet([ [ "@user_profile", JSON.stringify(profile), ], @@ -122,12 +98,12 @@ const AuthenticateJsx = ({navigation}) => { // Ensure the app has been created if (appJSON == null) { // 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", redirect_uris: REDIRECT_URI, scopes: "read write follow push", website: "https://github.com/natjms/resin", - }); + }).then(resp => resp.json()); await AsyncStorage .setItem("@app_object", JSON.stringify(app)) diff --git a/src/components/pages/profile.js b/src/components/pages/profile.js index 8c89d1c..d0171a9 100644 --- a/src/components/pages/profile.js +++ b/src/components/pages/profile.js @@ -13,6 +13,7 @@ import AsyncStorage from "@react-native-async-storage/async-storage"; import { activeOrNot } from "src/interface/interactions"; import { withoutHTML } from "src/interface/rendering"; +import * as requests from "src/requests"; import GridViewJsx from "src/components/posts/grid-view"; import { @@ -89,13 +90,15 @@ const TEST_THEIR_FOLLOWERS = [ { id: 6 }, ]; -function getMutuals(yours, theirs) { - // Where yours and theirs are arrays of followers, as returned by the oAPI +function getMutuals(yourFollowing, theirFollowers) { + // 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 asIDs = new Set(theirs.map(idify)); + const acctsArray = ({acct}) => acct; + 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}) => { @@ -118,62 +121,143 @@ const HTMLLink = ({link}) => { } } -const ProfileJsx = ({navigation}) => { - return ( - - - - ); -}; - 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 ( - - - + <> + { state.loaded + ? + + + : <> + } + ); } -const ProfileDisplayJsx = ({navigation}) => { - const accountName = navigation.getParam("acct", ""); - let [state, setState] = useState({ +const ProfileJsx = ({ navigation }) => { + const [state, setState] = useState({ 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 + ? + + + : <> + } + + ) +}; + +const RawProfileJsx = (props) => { + let [state, setState] = useState({ + own: props.own, + profile: props.profile, + notifs: props.notifs, + }); + const notif_pack = { active: require("assets/eva-icons/bell-unread.png"), inactive: require("assets/eva-icons/bell-black.png") } - useEffect(() => { - 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, - }); - }); - }, []); + const _handleFollow = () => {}; let profileButton; - if (state.own) { + if (props.own) { profileButton = ( { - navigation.navigate("Settings"); + props.navigation.navigate("Settings"); } }> @@ -183,7 +267,7 @@ const ProfileDisplayJsx = ({navigation}) => { ); } else { profileButton = ( - + Follow @@ -193,107 +277,101 @@ const ProfileDisplayJsx = ({navigation}) => { return ( - { state.loaded ? - <> - - - - - - {state.profile.display_name} - - - @{state.profile.username } - - - { - state.own ? - - { - navigation.navigate("Notifications"); - } - }> - - - - : - } - - - { state.profile.statuses_count } posts •  - { - const context = state.own ? - "People following you" - : "Your mutual followers with " + state.profile.display_name; - navigation.navigate("UserList", { - data: [/*Some array of users*/], - context: context - }); - } - }> - { - state.own ? - <>View followers - : <>{state.mutuals.length + " mutuals"} - } - - + + + + + + { props.profile.display_name} - - {state.profile.note} + + @{ props.profile.acct } - - { state.profile.fields - ? state.profile.fields.map((field, index) => ( - - - - { field.name } - - - - - - - )) - : <> - } - - {profileButton} - - { - navigation.navigate("ViewPost", { - id: id, - originTab: "Profile" + { + state.own ? + + { + props.navigation.navigate("Notifications"); + } + }> + + + + : + } + + + { props.profile.statuses_count } posts •  + { + const context = props.own ? + "People following you" + : "Your mutual followers with " + props.profile.display_name; + props.navigation.navigate("UserList", { + context: context, }); } - } /> - - : - } + }> + { + state.own ? + <>View followers + : <>{ props.mutuals + " mutuals" } + } + + + + + {props.profile.note} + + + { props.profile.fields + ? props.profile.fields.map((field, index) => ( + + + + { field.name } + + + + + + + )) + : <> + } + + {profileButton} + + + { + props.navigation.navigate("ViewPost", { + id: id, + originTab: "Profile" + }); + } + } /> ); }; @@ -371,5 +449,4 @@ const styles = { }, }; -export { ViewProfileJsx, ProfileDisplayJsx }; -export default ProfileJsx; +export { ViewProfileJsx, ProfileJsx as default }; diff --git a/src/requests.js b/src/requests.js index 9e8349a..3147c0a 100644 --- a/src/requests.js +++ b/src/requests.js @@ -28,3 +28,45 @@ export async function checkUnreadNotifications() { 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(); +}