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 * 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))
|
||||||
|
|
|
@ -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 •
|
{ props.profile.statuses_count } posts •
|
||||||
<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;
|
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue