Enable updating credentials from settings.js

Using this interface on Pixelfed will erase your profile's metadata. See #26 for
discussion and possible alternatives
This commit is contained in:
Nat 2021-05-23 01:33:13 -03:00
parent 860c5313ba
commit 80ec215be3
4 changed files with 125 additions and 164 deletions

34
package-lock.json generated
View File

@ -11,6 +11,7 @@
"@react-navigation/native": "5.1.1",
"@react-navigation/stack": "5.2.3",
"expo": "^38.0.9",
"expo-image-picker": "~8.3.0",
"expo-linking": "^1.0.7",
"expo-status-bar": "^1.0.2",
"expo-web-browser": "~8.3.1",
@ -3608,6 +3609,23 @@
"fontfaceobserver": "^2.1.0"
}
},
"node_modules/expo-image-picker": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/expo-image-picker/-/expo-image-picker-8.3.0.tgz",
"integrity": "sha512-HCF7SumPE4jXH+EhJ19Xw6G8Lxpms1z6Hxdq/kLlzvZGdzH4ZFBqp3NyTO7dtm0dXtjduljpNK7kQfwWtvmIWQ==",
"dependencies": {
"expo-permissions": "~9.0.1",
"uuid": "7.0.2"
}
},
"node_modules/expo-image-picker/node_modules/uuid": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.2.tgz",
"integrity": "sha512-vy9V/+pKG+5ZTYKf+VcphF5Oc6EFiu3W8Nv3P3zIh0EqVI80ZxOzuPfe9EHjkFNvf8+xuTHVeei4Drydlx4zjw==",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/expo-keep-awake": {
"version": "8.2.1",
"resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-8.2.1.tgz",
@ -12340,6 +12358,22 @@
"fontfaceobserver": "^2.1.0"
}
},
"expo-image-picker": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/expo-image-picker/-/expo-image-picker-8.3.0.tgz",
"integrity": "sha512-HCF7SumPE4jXH+EhJ19Xw6G8Lxpms1z6Hxdq/kLlzvZGdzH4ZFBqp3NyTO7dtm0dXtjduljpNK7kQfwWtvmIWQ==",
"requires": {
"expo-permissions": "~9.0.1",
"uuid": "7.0.2"
},
"dependencies": {
"uuid": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.2.tgz",
"integrity": "sha512-vy9V/+pKG+5ZTYKf+VcphF5Oc6EFiu3W8Nv3P3zIh0EqVI80ZxOzuPfe9EHjkFNvf8+xuTHVeei4Drydlx4zjw=="
}
}
},
"expo-keep-awake": {
"version": "8.2.1",
"resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-8.2.1.tgz",

View File

@ -14,6 +14,7 @@
"@react-navigation/native": "5.1.1",
"@react-navigation/stack": "5.2.3",
"expo": "^38.0.9",
"expo-image-picker": "~8.3.0",
"expo-linking": "^1.0.7",
"expo-status-bar": "^1.0.2",
"expo-web-browser": "~8.3.1",

View File

@ -9,49 +9,22 @@ import {
TouchableOpacity,
Dimensions,
} from "react-native";
import { FontAwesome } from '@expo/vector-icons';
import AsyncStorage from "@react-native-async-storage/async-storage";
import * as requests from "src/requests";
import * as ImagePicker from 'expo-image-picker';
import { withoutHTML } from "src/interface/rendering";
import * as requests from "src/requests";
import { ScreenWithBackBarJsx } from "src/components/navigation/navigators";
const TEST_IMAGE = "https://cache.desktopnexus.com/thumbseg/2255/2255124-bigthumbnail.jpg";
const TEST_PROFILE = {
username: "njms",
acct: "njms",
display_name: "Nat🔆",
locked: false,
bot: false,
note: "Yeah heart emoji.",
avatar: TEST_IMAGE,
followers_count: "1 jillion",
statuses_count: 334,
fields: [
{
name: "Blog",
value: "<a href=\"https://njms.ca\">https://njms.ca</a>",
verified_at: "some time"
},
{
name: "Github",
value: "<a href=\"https://github.com/natjms\">https://github.com/natjms</a>",
verified_at: null
}
]
};
const SettingsJsx = (props) => {
const [state, setState] = useState({
// Use Context to get this stuff eventually
profile: TEST_PROFILE,
newProfile: TEST_PROFILE,
loaded: false,
});
const fields = state.newProfile.fields;
const _handleLogout = async () => {
await requests.postForm(
`https://${state.instance}/oauth/revoke`,
@ -96,24 +69,76 @@ const SettingsJsx = (props) => {
setState({...state,
profile: profile,
newProfile: newProfile,
instance: instance,
appObject: appObject,
token: token,
accessToken: token.access_token,
// Malleable props that will actually go towards updating
// the profile credentials
locked: profile.locked,
newAvatar: {
uri: profile.avatar,
},
display_name: profile.display_name,
note: profile.note,
loaded: true,
})
});
}, []);
const _handleChangeProfilePhoto = async () => {
await ImagePicker.getCameraRollPermissionsAsync()
const { base64, uri } = await ImagePicker.launchImageLibraryAsync({
allowsEditing: true,
aspect: [1, 1],
});
setState({...state,
newAvatar: {
base64,
uri,
},
});
};
const _handleSaveProfile = async () => {
let params = {
display_name: state.display_name,
note: state.note,
locked: state.locked,
};
if (state.newAvatar.base64) {
let blob = fetch(state.newAvatar.base64).then(res => res.blob());
let filename = uri.split("/")[uri.split("/").length - 1];
params.avatar = new File([blob], filename);
}
const newProfile = await fetch(
`https://${state.instance}/api/v1/accounts/update_credentials`,
{
method: "PATCH",
body: requests.objectToForm(params),
headers: { "Authorization": `Bearer ${state.accessToken}`, }
}
).then(resp => resp.json());
await AsyncStorage.setItem("@user_profile", JSON.stringify(newProfile));
props.navigation.navigate("Profile");
};
return (
<>
{ state.loaded
? <ScreenWithBackBarJsx navigation = { props.navigation }>
<View style = { styles.avatar.container }>
<Image
source = { { uri: state.profile.avatar } }
source = { { uri: state.newAvatar.uri } }
style = { styles.avatar.image }/>
<TouchableOpacity>
<TouchableOpacity onPress = { _handleChangeProfilePhoto }>
<Text style = { styles.avatar.change }>
Change profile photo
</Text>
@ -124,24 +149,11 @@ const SettingsJsx = (props) => {
<TextInput
style = { styles.bar }
placeholder = { "Display name" }
value = { state.newProfile.display_name }
value = { state.display_name }
onChangeText = {
(value) => {
setState({...state,
newProfile: {...state.newProfile, display_name: value}
});
}
}/>
<Text style = { styles.label }>User name</Text>
<TextInput
style = { styles.bar }
placeholder = { "User name" }
value = { state.newProfile.username }
onChangeText = {
(value) => {
setState({...state,
newProfile: {...state.newProfile, username: value}
display_name: value,
});
}
}/>
@ -156,110 +168,40 @@ const SettingsJsx = (props) => {
}
multiline = { true }
placeholder = { "Bio" }
value = { withoutHTML(state.newProfile.note) }
// HACK: Using withoutHTML here is dangerous
value = { withoutHTML(state.note) }
onChangeText = {
(value) => {
setState({...state,
newProfile: {...state.newProfile, note: value}
note: value
});
}
}/>
{
fields.map((field, i) =>
<View
style = { styles.fields.container }
key = { i }>
<TouchableOpacity
onPress = {
() => {
let newFields;
if (fields.length == 1) {
newFields = [{ name: "", value: "" }];
} else {
newFields = state.newProfile.fields;
newFields.splice(i, 1);
}
() => (
setState({...state,
newProfile: {...state.newProfile,
fields: newFields,
},
});
}
}>
<Image
style = {
[
styles.fields.cross,
fields.length == 1
&& fields[0].name == ""
&& fields[0].value == ""
? { visibility: "hidden" }
: {}
]
}
source = { require("assets/eva-icons/close.png") }/>
</TouchableOpacity>
<View style = { styles.fields.subContainer }>
<Text style = { styles.label }>Name</Text>
<TextInput
style = { [styles.bar, styles.fields.cell] }
placeholder = { "Name" }
value = { withoutHTML(fields[i].name) }
onChangeText = {
(text) => {
let newFields = fields;
newFields[i] = {...newFields[i],
name: text,
};
setState({...state,
newProfile: {...state.newProfile,
fields: newFields,
},
});
}
} />
</View>
<View style = { styles.fields.subContainer }>
<Text style = { styles.label }>Value</Text>
<TextInput
style = { [styles.bar, styles.fields.cell] }
placeholder = { "Value" }
value = { withoutHTML(fields[i].value) }
onChangeText = {
(text) => {
let newFields = fields;
newFields[i] = {...newFields[i],
value: text,
};
setState({...state,
newProfile: {...state.newProfile,
fields: newFields,
},
});
}
} />
</View>
</View>
locked: !state.locked
})
)
}
<TouchableOpacity
onPress = {
() => {
setState({...state,
newProfile: {...state.newProfile,
fields: state.newProfile.fields.concat({ name: "", value: ""}),
},
});
}
}>
<Image
style = { styles.fields.plus }
source = { require("assets/eva-icons/plus.png") } />
<View style = { styles.check.container }>
<>
{ !state.locked
? <FontAwesome name="square-o" size={24} color="black" />
: <FontAwesome name="check-square-o" size={24} color="black" />
}
</>
<Text style = { styles.check.label }>
Manually approve follow requests?
</Text>
</View>
</TouchableOpacity>
<TouchableOpacity style = { styles.button.container }>
<TouchableOpacity
onPress = { _handleSaveProfile }
style = { styles.button.container }>
<Text style = { styles.button.text }> Save Profile </Text>
</TouchableOpacity>
<TouchableOpacity
@ -314,30 +256,14 @@ const styles = {
padding: 10,
},
},
fields: {
check: {
container: {
flex: 1,
flexDirection: "row",
alignItems: "flex-end",
alignItems: "center",
padding: 10,
},
cross: {
width: 30,
height: 30,
marginRight: 10,
marginBottom: 10,
},
plus: {
width: 30,
height: 30,
marginLeft: "auto",
marginRight: "auto",
marginTop: 10,
},
subContainer: {
flexGrow: 0.5,
},
cell: {
width: SCREEN_WIDTH / 2.5,
label: {
paddingLeft: 10,
},
},
button: {

View File

@ -4,7 +4,7 @@ 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 }];
function objectToForm(obj) {
export function objectToForm(obj) {
let form = new FormData();
Object.keys(obj).forEach(key =>