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:
parent
860c5313ba
commit
80ec215be3
|
@ -11,6 +11,7 @@
|
||||||
"@react-navigation/native": "5.1.1",
|
"@react-navigation/native": "5.1.1",
|
||||||
"@react-navigation/stack": "5.2.3",
|
"@react-navigation/stack": "5.2.3",
|
||||||
"expo": "^38.0.9",
|
"expo": "^38.0.9",
|
||||||
|
"expo-image-picker": "~8.3.0",
|
||||||
"expo-linking": "^1.0.7",
|
"expo-linking": "^1.0.7",
|
||||||
"expo-status-bar": "^1.0.2",
|
"expo-status-bar": "^1.0.2",
|
||||||
"expo-web-browser": "~8.3.1",
|
"expo-web-browser": "~8.3.1",
|
||||||
|
@ -3608,6 +3609,23 @@
|
||||||
"fontfaceobserver": "^2.1.0"
|
"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": {
|
"node_modules/expo-keep-awake": {
|
||||||
"version": "8.2.1",
|
"version": "8.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-8.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-8.2.1.tgz",
|
||||||
|
@ -12340,6 +12358,22 @@
|
||||||
"fontfaceobserver": "^2.1.0"
|
"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": {
|
"expo-keep-awake": {
|
||||||
"version": "8.2.1",
|
"version": "8.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-8.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-8.2.1.tgz",
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
"@react-navigation/native": "5.1.1",
|
"@react-navigation/native": "5.1.1",
|
||||||
"@react-navigation/stack": "5.2.3",
|
"@react-navigation/stack": "5.2.3",
|
||||||
"expo": "^38.0.9",
|
"expo": "^38.0.9",
|
||||||
|
"expo-image-picker": "~8.3.0",
|
||||||
"expo-linking": "^1.0.7",
|
"expo-linking": "^1.0.7",
|
||||||
"expo-status-bar": "^1.0.2",
|
"expo-status-bar": "^1.0.2",
|
||||||
"expo-web-browser": "~8.3.1",
|
"expo-web-browser": "~8.3.1",
|
||||||
|
|
|
@ -9,49 +9,22 @@ import {
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
Dimensions,
|
Dimensions,
|
||||||
} from "react-native";
|
} from "react-native";
|
||||||
|
import { FontAwesome } from '@expo/vector-icons';
|
||||||
|
|
||||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
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 { withoutHTML } from "src/interface/rendering";
|
||||||
import * as requests from "src/requests";
|
|
||||||
|
|
||||||
import { ScreenWithBackBarJsx } from "src/components/navigation/navigators";
|
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 SettingsJsx = (props) => {
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
// Use Context to get this stuff eventually
|
|
||||||
profile: TEST_PROFILE,
|
|
||||||
newProfile: TEST_PROFILE,
|
|
||||||
loaded: false,
|
loaded: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const fields = state.newProfile.fields;
|
|
||||||
|
|
||||||
const _handleLogout = async () => {
|
const _handleLogout = async () => {
|
||||||
await requests.postForm(
|
await requests.postForm(
|
||||||
`https://${state.instance}/oauth/revoke`,
|
`https://${state.instance}/oauth/revoke`,
|
||||||
|
@ -96,24 +69,76 @@ const SettingsJsx = (props) => {
|
||||||
|
|
||||||
setState({...state,
|
setState({...state,
|
||||||
profile: profile,
|
profile: profile,
|
||||||
newProfile: newProfile,
|
|
||||||
instance: instance,
|
instance: instance,
|
||||||
appObject: appObject,
|
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,
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
{ state.loaded
|
{ state.loaded
|
||||||
? <ScreenWithBackBarJsx navigation = { props.navigation }>
|
? <ScreenWithBackBarJsx navigation = { props.navigation }>
|
||||||
<View style = { styles.avatar.container }>
|
<View style = { styles.avatar.container }>
|
||||||
<Image
|
<Image
|
||||||
source = { { uri: state.profile.avatar } }
|
source = { { uri: state.newAvatar.uri } }
|
||||||
style = { styles.avatar.image }/>
|
style = { styles.avatar.image }/>
|
||||||
<TouchableOpacity>
|
<TouchableOpacity onPress = { _handleChangeProfilePhoto }>
|
||||||
<Text style = { styles.avatar.change }>
|
<Text style = { styles.avatar.change }>
|
||||||
Change profile photo
|
Change profile photo
|
||||||
</Text>
|
</Text>
|
||||||
|
@ -124,24 +149,11 @@ const SettingsJsx = (props) => {
|
||||||
<TextInput
|
<TextInput
|
||||||
style = { styles.bar }
|
style = { styles.bar }
|
||||||
placeholder = { "Display name" }
|
placeholder = { "Display name" }
|
||||||
value = { state.newProfile.display_name }
|
value = { state.display_name }
|
||||||
onChangeText = {
|
onChangeText = {
|
||||||
(value) => {
|
(value) => {
|
||||||
setState({...state,
|
setState({...state,
|
||||||
newProfile: {...state.newProfile, display_name: value}
|
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}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}/>
|
}/>
|
||||||
|
@ -156,110 +168,40 @@ const SettingsJsx = (props) => {
|
||||||
}
|
}
|
||||||
multiline = { true }
|
multiline = { true }
|
||||||
placeholder = { "Bio" }
|
placeholder = { "Bio" }
|
||||||
value = { withoutHTML(state.newProfile.note) }
|
// HACK: Using withoutHTML here is dangerous
|
||||||
|
value = { withoutHTML(state.note) }
|
||||||
onChangeText = {
|
onChangeText = {
|
||||||
(value) => {
|
(value) => {
|
||||||
setState({...state,
|
setState({...state,
|
||||||
newProfile: {...state.newProfile, note: value}
|
note: value
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}/>
|
}/>
|
||||||
{
|
|
||||||
fields.map((field, i) =>
|
|
||||||
<View
|
|
||||||
style = { styles.fields.container }
|
|
||||||
key = { i }>
|
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress = {
|
onPress = {
|
||||||
() => {
|
() => (
|
||||||
let newFields;
|
|
||||||
if (fields.length == 1) {
|
|
||||||
newFields = [{ name: "", value: "" }];
|
|
||||||
} else {
|
|
||||||
newFields = state.newProfile.fields;
|
|
||||||
newFields.splice(i, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
setState({...state,
|
setState({...state,
|
||||||
newProfile: {...state.newProfile,
|
locked: !state.locked
|
||||||
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>
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
<TouchableOpacity
|
|
||||||
onPress = {
|
|
||||||
() => {
|
|
||||||
setState({...state,
|
|
||||||
newProfile: {...state.newProfile,
|
|
||||||
fields: state.newProfile.fields.concat({ name: "", value: ""}),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}>
|
}>
|
||||||
<Image
|
<View style = { styles.check.container }>
|
||||||
style = { styles.fields.plus }
|
<>
|
||||||
source = { require("assets/eva-icons/plus.png") } />
|
{ !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>
|
||||||
<TouchableOpacity style = { styles.button.container }>
|
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress = { _handleSaveProfile }
|
||||||
|
style = { styles.button.container }>
|
||||||
<Text style = { styles.button.text }> Save Profile </Text>
|
<Text style = { styles.button.text }> Save Profile </Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
|
@ -314,30 +256,14 @@ const styles = {
|
||||||
padding: 10,
|
padding: 10,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
fields: {
|
check: {
|
||||||
container: {
|
container: {
|
||||||
flex: 1,
|
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "flex-end",
|
alignItems: "center",
|
||||||
|
padding: 10,
|
||||||
},
|
},
|
||||||
cross: {
|
label: {
|
||||||
width: 30,
|
paddingLeft: 10,
|
||||||
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,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
button: {
|
button: {
|
||||||
|
|
|
@ -4,7 +4,7 @@ const TEST_NOTIFICATIONS = [{ id: 1 }, { id: 2 }];
|
||||||
const TEST_NEW_NOTIFICATIONS_1 = [{ id: 1 }, { id: 2 }];
|
const TEST_NEW_NOTIFICATIONS_1 = [{ id: 1 }, { id: 2 }];
|
||||||
const TEST_NEW_NOTIFICATIONS_2 = [{ id: 1 }, { id: 2 }, { id: 3 }];
|
const TEST_NEW_NOTIFICATIONS_2 = [{ id: 1 }, { id: 2 }, { id: 3 }];
|
||||||
|
|
||||||
function objectToForm(obj) {
|
export function objectToForm(obj) {
|
||||||
let form = new FormData();
|
let form = new FormData();
|
||||||
|
|
||||||
Object.keys(obj).forEach(key =>
|
Object.keys(obj).forEach(key =>
|
||||||
|
|
Loading…
Reference in New Issue