Improve loading of profile data
This commit is contained in:
parent
31d8282bdc
commit
856f215e36
|
@ -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))
|
||||
|
|
|
@ -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 (
|
||||
<ScreenWithTrayJsx
|
||||
active = "Profile"
|
||||
navigation = { navigation }
|
||||
active = "Profile">
|
||||
<ProfileDisplayJsx navigation = { navigation }/>
|
||||
</ScreenWithTrayJsx>
|
||||
);
|
||||
};
|
||||
|
||||
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 (
|
||||
<ScreenWithFullNavigationJsx
|
||||
active = { navigation.getParam("originTab") }
|
||||
navigation = { navigation }>
|
||||
<ProfileDisplayJsx navigation = { navigation } />
|
||||
</ScreenWithFullNavigationJsx>
|
||||
<>
|
||||
{ state.loaded
|
||||
? <ScreenWithFullNavigationJsx
|
||||
active = { navigation.getParam("originTab") }
|
||||
navigation = { navigation }>
|
||||
<RawProfileJsx
|
||||
profile = { state.profile }
|
||||
notifs = { state.notifs }
|
||||
posts = { TEST_POSTS }/>
|
||||
</ScreenWithFullNavigationJsx>
|
||||
: <></>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
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
|
||||
? <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 = {
|
||||
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 = (
|
||||
<TouchableOpacity
|
||||
onPress = {
|
||||
() => {
|
||||
navigation.navigate("Settings");
|
||||
props.navigation.navigate("Settings");
|
||||
}
|
||||
}>
|
||||
<View style = { styles.button }>
|
||||
|
@ -183,7 +267,7 @@ const ProfileDisplayJsx = ({navigation}) => {
|
|||
);
|
||||
} else {
|
||||
profileButton = (
|
||||
<TouchableOpacity>
|
||||
<TouchableOpacity onPress = { _handleFollow }>
|
||||
<View style = { styles.button }>
|
||||
<Text style = { styles.buttonText }>Follow</Text>
|
||||
</View>
|
||||
|
@ -193,107 +277,101 @@ const ProfileDisplayJsx = ({navigation}) => {
|
|||
|
||||
return (
|
||||
<View>
|
||||
{ state.loaded ?
|
||||
<>
|
||||
<View style = { styles.jumbotron }>
|
||||
<View style = { styles.profileHeader }>
|
||||
<Image
|
||||
source = { { uri: state.profile.avatar } }
|
||||
style = { styles.avatar } />
|
||||
<View>
|
||||
<Text
|
||||
style = { styles.displayName }>
|
||||
{state.profile.display_name}
|
||||
</Text>
|
||||
<Text style={ styles.strong }>
|
||||
@{state.profile.username }
|
||||
</Text>
|
||||
</View>
|
||||
{
|
||||
state.own ?
|
||||
<View style = { styles.profileContextContainer }>
|
||||
<TouchableOpacity
|
||||
onPress = {
|
||||
() => {
|
||||
navigation.navigate("Notifications");
|
||||
}
|
||||
}>
|
||||
<Image
|
||||
source = {
|
||||
activeOrNot(
|
||||
state.unreadNotifications,
|
||||
notif_pack
|
||||
)
|
||||
}
|
||||
style = { styles.profileHeaderIcon } />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
: <ModerateMenuJsx
|
||||
triggerStyle = { styles.profileHeaderIcon }
|
||||
containerStyle = { styles.profileContextContainer } />
|
||||
}
|
||||
</View>
|
||||
<Text style = { styles.accountStats }>
|
||||
{ state.profile.statuses_count } posts •
|
||||
<Text onPress = {
|
||||
() => {
|
||||
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"}</>
|
||||
}
|
||||
|
||||
</Text>
|
||||
<View style = { styles.jumbotron }>
|
||||
<View style = { styles.profileHeader }>
|
||||
<Image
|
||||
source = { { uri: props.profile.avatar } }
|
||||
style = { styles.avatar } />
|
||||
<View>
|
||||
<Text
|
||||
style = { styles.displayName }>
|
||||
{ props.profile.display_name}
|
||||
</Text>
|
||||
<Text style = { styles.note }>
|
||||
{state.profile.note}
|
||||
<Text style={ styles.strong }>
|
||||
@{ props.profile.acct }
|
||||
</Text>
|
||||
<View style = { styles.fields.container }>
|
||||
{ state.profile.fields
|
||||
? state.profile.fields.map((field, index) => (
|
||||
<View
|
||||
style = { styles.fields.row }
|
||||
key = { index }>
|
||||
<View style = { styles.fields.cell.name }>
|
||||
<Text style = {
|
||||
{ textAlign: "center", }
|
||||
}>
|
||||
{ field.name }
|
||||
</Text>
|
||||
</View>
|
||||
<View style = { styles.fields.cell.value }>
|
||||
<HTMLLink link = { field.value }/>
|
||||
</View>
|
||||
</View>
|
||||
))
|
||||
: <></>
|
||||
}
|
||||
</View>
|
||||
{profileButton}
|
||||
</View>
|
||||
|
||||
<GridViewJsx
|
||||
posts = { TEST_POSTS }
|
||||
openPostCallback = {
|
||||
(id) => {
|
||||
navigation.navigate("ViewPost", {
|
||||
id: id,
|
||||
originTab: "Profile"
|
||||
{
|
||||
state.own ?
|
||||
<View style = { styles.profileContextContainer }>
|
||||
<TouchableOpacity
|
||||
onPress = {
|
||||
() => {
|
||||
props.navigation.navigate("Notifications");
|
||||
}
|
||||
}>
|
||||
<Image
|
||||
source = {
|
||||
activeOrNot(
|
||||
props.notifs.unread,
|
||||
notif_pack
|
||||
)
|
||||
}
|
||||
style = { styles.profileHeaderIcon } />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
: <ModerateMenuJsx
|
||||
triggerStyle = { styles.profileHeaderIcon }
|
||||
containerStyle = { styles.profileContextContainer } />
|
||||
}
|
||||
</View>
|
||||
<Text style = { styles.accountStats }>
|
||||
{ props.profile.statuses_count } posts •
|
||||
<Text onPress = {
|
||||
() => {
|
||||
const context = props.own ?
|
||||
"People following you"
|
||||
: "Your mutual followers with " + props.profile.display_name;
|
||||
props.navigation.navigate("UserList", {
|
||||
context: context,
|
||||
});
|
||||
}
|
||||
} />
|
||||
</>
|
||||
: <View></View>
|
||||
}
|
||||
}>
|
||||
{
|
||||
state.own ?
|
||||
<>View followers</>
|
||||
: <>{ props.mutuals + " mutuals" }</>
|
||||
}
|
||||
|
||||
</Text>
|
||||
</Text>
|
||||
<Text style = { styles.note }>
|
||||
{props.profile.note}
|
||||
</Text>
|
||||
<View style = { styles.fields.container }>
|
||||
{ props.profile.fields
|
||||
? props.profile.fields.map((field, index) => (
|
||||
<View
|
||||
style = { styles.fields.row }
|
||||
key = { index }>
|
||||
<View style = { styles.fields.cell.name }>
|
||||
<Text style = {
|
||||
{ textAlign: "center", }
|
||||
}>
|
||||
{ field.name }
|
||||
</Text>
|
||||
</View>
|
||||
<View style = { styles.fields.cell.value }>
|
||||
<HTMLLink link = { field.value }/>
|
||||
</View>
|
||||
</View>
|
||||
))
|
||||
: <></>
|
||||
}
|
||||
</View>
|
||||
{profileButton}
|
||||
</View>
|
||||
|
||||
<GridViewJsx
|
||||
posts = { props.posts }
|
||||
openPostCallback = {
|
||||
(id) => {
|
||||
props.navigation.navigate("ViewPost", {
|
||||
id: id,
|
||||
originTab: "Profile"
|
||||
});
|
||||
}
|
||||
} />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
@ -371,5 +449,4 @@ const styles = {
|
|||
},
|
||||
};
|
||||
|
||||
export { ViewProfileJsx, ProfileDisplayJsx };
|
||||
export default ProfileJsx;
|
||||
export { ViewProfileJsx, ProfileJsx as default };
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue