fix(native/profile): round avatar crop frame to match circular avatar display

Avatars render as circles everywhere (AppHeader, PostCard, profile
page), so a square crop frame let users compose an image that looked
fine in the cropper and got visibly clipped (lost corners, off-center
faces) after upload.

Switched the crop frame to a perfect circle by setting borderRadius =
CROP_SIZE / 2 on both the frame and the overflow mask. Replaced the
four square corner markers with a single thin white ring overlay
around the circle. Output is still a 512×512 JPEG — the consumer-side
border-radius does the visual circle, so the underlying square is
storage-agnostic and re-usable if we ever surface a non-circular
avatar elsewhere.
This commit is contained in:
chahinebrini 2026-05-15 23:55:57 +02:00
parent 5d74214822
commit 0fc8ab1687

View File

@ -134,8 +134,17 @@ export function AvatarCropSheet({ imageUri, onConfirm, onCancel }: Props) {
</View> </View>
<View style={styles.body}> <View style={styles.body}>
<View style={[styles.cropFrame, { width: CROP_SIZE, height: CROP_SIZE }]}> <View
<View style={styles.cropOverflow}> style={[
styles.cropFrame,
{
width: CROP_SIZE,
height: CROP_SIZE,
borderRadius: CROP_SIZE / 2,
},
]}
>
<View style={[styles.cropOverflow, { borderRadius: CROP_SIZE / 2 }]}>
<GestureDetector gesture={composed}> <GestureDetector gesture={composed}>
<Animated.View style={imageStyle}> <Animated.View style={imageStyle}>
{imageUri ? ( {imageUri ? (
@ -149,10 +158,13 @@ export function AvatarCropSheet({ imageUri, onConfirm, onCancel }: Props) {
</GestureDetector> </GestureDetector>
</View> </View>
<View style={styles.cornerTL} /> <View
<View style={styles.cornerTR} /> pointerEvents="none"
<View style={styles.cornerBL} /> style={[
<View style={styles.cornerBR} /> styles.ringOverlay,
{ borderRadius: CROP_SIZE / 2 },
]}
/>
</View> </View>
<Text style={[styles.hint, { color: colors.textMuted }]}> <Text style={[styles.hint, { color: colors.textMuted }]}>
@ -179,8 +191,7 @@ export function AvatarCropSheet({ imageUri, onConfirm, onCancel }: Props) {
); );
} }
const CORNER = 18; const RING_BORDER = 2;
const BORDER = 2.5;
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
@ -214,7 +225,6 @@ const styles = StyleSheet.create({
gap: 20, gap: 20,
}, },
cropFrame: { cropFrame: {
borderRadius: 12,
overflow: 'hidden', overflow: 'hidden',
position: 'relative', position: 'relative',
backgroundColor: '#111', backgroundColor: '#111',
@ -224,49 +234,14 @@ const styles = StyleSheet.create({
height: '100%', height: '100%',
overflow: 'hidden', overflow: 'hidden',
}, },
cornerTL: { ringOverlay: {
position: 'absolute', position: 'absolute',
top: 0, top: 0,
left: 0, left: 0,
width: CORNER,
height: CORNER,
borderTopWidth: BORDER,
borderLeftWidth: BORDER,
borderColor: '#fff',
borderTopLeftRadius: 12,
},
cornerTR: {
position: 'absolute',
top: 0,
right: 0, right: 0,
width: CORNER,
height: CORNER,
borderTopWidth: BORDER,
borderRightWidth: BORDER,
borderColor: '#fff',
borderTopRightRadius: 12,
},
cornerBL: {
position: 'absolute',
bottom: 0, bottom: 0,
left: 0, borderWidth: RING_BORDER,
width: CORNER, borderColor: 'rgba(255,255,255,0.85)',
height: CORNER,
borderBottomWidth: BORDER,
borderLeftWidth: BORDER,
borderColor: '#fff',
borderBottomLeftRadius: 12,
},
cornerBR: {
position: 'absolute',
bottom: 0,
right: 0,
width: CORNER,
height: CORNER,
borderBottomWidth: BORDER,
borderRightWidth: BORDER,
borderColor: '#fff',
borderBottomRightRadius: 12,
}, },
hint: { hint: {
fontSize: 12, fontSize: 12,