From 6ee48ffb9d628a04c487b73b222be76241ff3ec4 Mon Sep 17 00:00:00 2001
From: Mohamed Bassem <me@mbassem.com>
Date: Mon, 29 Dec 2025 10:23:36 +0200
Subject: feat(mobile): make the settings menu look more native (#2307)

* feat(mobile): make the settings menu look more native

* more fixes

* review comments
---
 apps/mobile/components/ui/Avatar.tsx | 88 ++++++++++++++++++++++++++++++++++++
 1 file changed, 88 insertions(+)
 create mode 100644 apps/mobile/components/ui/Avatar.tsx

(limited to 'apps/mobile/components/ui/Avatar.tsx')

diff --git a/apps/mobile/components/ui/Avatar.tsx b/apps/mobile/components/ui/Avatar.tsx
new file mode 100644
index 00000000..923c634e
--- /dev/null
+++ b/apps/mobile/components/ui/Avatar.tsx
@@ -0,0 +1,88 @@
+import * as React from "react";
+import { Image, View } from "react-native";
+import { Text } from "@/components/ui/Text";
+import { useAssetUrl } from "@/lib/hooks";
+import { cn } from "@/lib/utils";
+
+interface AvatarProps {
+  image?: string | null;
+  name?: string | null;
+  size?: number;
+  className?: string;
+  imageClassName?: string;
+  fallbackClassName?: string;
+}
+
+function isExternalUrl(url: string) {
+  return url.startsWith("http://") || url.startsWith("https://");
+}
+
+export function Avatar({
+  image,
+  name,
+  size = 40,
+  className,
+  imageClassName,
+  fallbackClassName,
+}: AvatarProps) {
+  const [imageError, setImageError] = React.useState(false);
+  const assetUrl = useAssetUrl(image ?? "");
+
+  const imageUrl = React.useMemo(() => {
+    if (!image) return null;
+    return isExternalUrl(image)
+      ? {
+          uri: image,
+        }
+      : assetUrl;
+  }, [image]);
+
+  React.useEffect(() => {
+    setImageError(false);
+  }, [image]);
+
+  const initials = React.useMemo(() => {
+    if (!name) return "U";
+    return name.charAt(0).toUpperCase();
+  }, [name]);
+
+  const showFallback = !imageUrl || imageError;
+
+  return (
+    <View
+      className={cn("overflow-hidden bg-black", className)}
+      style={{
+        width: size,
+        height: size,
+        borderRadius: size / 2,
+      }}
+    >
+      {showFallback ? (
+        <View
+          className={cn(
+            "flex h-full w-full items-center justify-center bg-black",
+            fallbackClassName,
+          )}
+        >
+          <Text
+            className="text-white"
+            style={{
+              fontSize: size * 0.4,
+              lineHeight: size * 0.4,
+              textAlign: "center",
+            }}
+          >
+            {initials}
+          </Text>
+        </View>
+      ) : (
+        <Image
+          source={imageUrl}
+          className={cn("h-full w-full", imageClassName)}
+          style={{ resizeMode: "cover" }}
+          onError={() => setImageError(true)}
+        />
+      )}
+    </View>
+  );
+}
-- 
cgit v1.2.3-70-g09d2

