UNPKG

@oxyhq/services

Version:

OxyHQ Expo/React Native SDK — UI components, screens, and native features

411 lines (410 loc) 14.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _react = _interopRequireWildcard(require("react")); var _reactNative = require("react-native"); var _index = require("../styles/index.js"); var _Avatar = _interopRequireDefault(require("../components/Avatar.js")); var _index2 = require("../components/index.js"); var _vectorIcons = require("@expo/vector-icons"); var _useI18n = require("../hooks/useI18n.js"); var _OxyContext = require("../context/OxyContext.js"); var _core = require("@oxyhq/core"); var _jsxRuntime = require("react/jsx-runtime"); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); } const PAGE_SIZE = 20; const UserListScreen = ({ userId, mode, initialCount, theme, goBack, navigate }) => { const { oxyServices, user: currentUser } = (0, _OxyContext.useOxy)(); const [users, setUsers] = (0, _react.useState)([]); const [total, setTotal] = (0, _react.useState)(initialCount ?? 0); const [isLoading, setIsLoading] = (0, _react.useState)(true); const [isLoadingMore, setIsLoadingMore] = (0, _react.useState)(false); const [isRefreshing, setIsRefreshing] = (0, _react.useState)(false); const [error, setError] = (0, _react.useState)(null); const [hasMore, setHasMore] = (0, _react.useState)(true); const colors = (0, _index.useThemeColors)(theme ?? 'light'); const styles = createStyles(colors); const { t } = (0, _useI18n.useI18n)(); const currentUserId = currentUser?.id || currentUser?._id; const fetchUsers = (0, _react.useCallback)(async (offset = 0, isRefresh = false) => { if (!userId) { setError('No user ID provided'); setIsLoading(false); return; } try { if (isRefresh) { setIsRefreshing(true); } else if (offset === 0) { setIsLoading(true); } else { setIsLoadingMore(true); } setError(null); const response = mode === 'followers' ? await oxyServices.getUserFollowers(userId, { limit: PAGE_SIZE, offset }) : await oxyServices.getUserFollowing(userId, { limit: PAGE_SIZE, offset }); const newUsers = mode === 'followers' ? response.followers : response.following; if (offset === 0 || isRefresh) { setUsers(newUsers); } else { setUsers(prev => [...prev, ...newUsers]); } setTotal(response.total); setHasMore(response.hasMore); } catch (err) { _core.logger.error(`Failed to fetch ${mode}`, err instanceof Error ? err : new Error(String(err)), { component: 'UserListScreen' }); setError(`Failed to load ${mode}. Please try again.`); } finally { setIsLoading(false); setIsLoadingMore(false); setIsRefreshing(false); } }, [userId, mode, oxyServices]); (0, _react.useEffect)(() => { fetchUsers(0); }, [fetchUsers]); const handleLoadMore = (0, _react.useCallback)(() => { if (!isLoadingMore && hasMore && !isLoading) { fetchUsers(users.length); } }, [isLoadingMore, hasMore, isLoading, users.length, fetchUsers]); const handleRefresh = (0, _react.useCallback)(() => { fetchUsers(0, true); }, [fetchUsers]); const handleUserPress = (0, _react.useCallback)(user => { const targetUserId = user.id || user._id; if (targetUserId && navigate) { navigate('Profile', { userId: targetUserId }); } }, [navigate]); const renderUser = (0, _react.useCallback)(({ item }) => { const itemUserId = item.id || item._id || ''; const isCurrentUser = itemUserId === currentUserId; const description = typeof item.description === 'string' ? item.description : ''; return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, { style: styles.userItem, onPress: () => handleUserPress(item), activeOpacity: 0.7, children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_Avatar.default, { uri: item.avatar ? oxyServices.getFileDownloadUrl(item.avatar, 'thumb') : undefined, name: item.username || item.name?.full, size: 48 }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: styles.userInfo, children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: styles.userName, numberOfLines: 1, children: item.name?.full || item.username || 'Unknown User' }), item.username && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, { style: styles.userHandle, numberOfLines: 1, children: ["@", item.username] }), description ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: styles.userBio, numberOfLines: 2, children: description }) : null] }), !isCurrentUser && itemUserId ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, { style: styles.followButtonWrapper, children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_index2.FollowButton, { userId: itemUserId, size: "small" }) }) : null] }); }, [colors, styles, handleUserPress, currentUserId, oxyServices]); const renderEmpty = (0, _react.useCallback)(() => { if (isLoading) return null; return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: styles.emptyContainer, children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, { name: mode === 'followers' ? 'people-outline' : 'heart-outline', size: 64, color: colors.secondaryText }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: styles.emptyTitle, children: mode === 'followers' ? t('userList.noFollowers') || 'No followers yet' : t('userList.noFollowing') || 'Not following anyone' }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: styles.emptySubtitle, children: mode === 'followers' ? t('userList.noFollowersDesc') || 'When people follow this user, they will appear here.' : t('userList.noFollowingDesc') || 'When this user follows people, they will appear here.' })] }); }, [isLoading, mode, colors, styles, t]); const renderFooter = (0, _react.useCallback)(() => { if (!isLoadingMore) return null; return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, { style: styles.footerLoader, children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, { size: "small", color: colors.primary }) }); }, [isLoadingMore, colors, styles]); const title = mode === 'followers' ? t('userList.followers') || 'Followers' : t('userList.following') || 'Following'; if (isLoading && users.length === 0) { return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: styles.container, children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: styles.header, children: [goBack && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, { onPress: goBack, style: styles.backButton, children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, { name: "arrow-back", size: 24, color: colors.text }) }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: styles.headerTitle, children: title }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, { style: styles.headerRight })] }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, { style: styles.loadingContainer, children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, { size: "large", color: colors.primary }) })] }); } if (error) { return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: styles.container, children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: styles.header, children: [goBack && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, { onPress: goBack, style: styles.backButton, children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, { name: "arrow-back", size: 24, color: colors.text }) }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: styles.headerTitle, children: title }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, { style: styles.headerRight })] }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: styles.errorContainer, children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, { name: "alert-circle", size: 48, color: colors.error }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: styles.errorText, children: error }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, { style: styles.retryButton, onPress: () => fetchUsers(0), children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: styles.retryButtonText, children: t('common.retry') || 'Retry' }) })] })] }); } return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: styles.container, children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: styles.header, children: [goBack && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, { onPress: goBack, style: styles.backButton, children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, { name: "arrow-back", size: 24, color: colors.text }) }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: styles.headerTitleContainer, children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: styles.headerTitle, children: title }), total > 0 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: styles.headerCount, children: total })] }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, { style: styles.headerRight })] }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.FlatList, { data: users, renderItem: renderUser, keyExtractor: (item, index) => item.id || item._id || `user-${index}`, contentContainerStyle: styles.listContent, ItemSeparatorComponent: () => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, { style: styles.separator }), ListEmptyComponent: renderEmpty, ListFooterComponent: renderFooter, onEndReached: handleLoadMore, onEndReachedThreshold: 0.3, refreshControl: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.RefreshControl, { refreshing: isRefreshing, onRefresh: handleRefresh, tintColor: colors.primary, colors: [colors.primary] }) })] }); }; const createStyles = colors => _reactNative.StyleSheet.create({ container: { flex: 1, backgroundColor: colors.background }, header: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 16, paddingVertical: 12, borderBottomWidth: 1, borderBottomColor: colors.border }, backButton: { padding: 8, marginRight: 8 }, headerTitleContainer: { flex: 1, flexDirection: 'row', alignItems: 'center' }, headerTitle: { fontSize: 18, fontWeight: '600', color: colors.text }, headerCount: { fontSize: 16, color: colors.secondaryText, marginLeft: 8 }, headerRight: { width: 40 }, loadingContainer: { flex: 1, justifyContent: 'center', alignItems: 'center' }, errorContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', paddingHorizontal: 32 }, errorText: { fontSize: 16, color: colors.error, textAlign: 'center', marginTop: 16, marginBottom: 24 }, retryButton: { backgroundColor: colors.primary, paddingHorizontal: 24, paddingVertical: 12, borderRadius: 8 }, retryButtonText: { color: '#fff', fontSize: 16, fontWeight: '600' }, listContent: { flexGrow: 1 }, userItem: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 16, paddingVertical: 12 }, userInfo: { flex: 1, marginLeft: 12, marginRight: 8 }, userName: { fontSize: 16, fontWeight: '600', color: colors.text }, userHandle: { fontSize: 14, color: colors.secondaryText, marginTop: 2 }, userBio: { fontSize: 14, color: colors.text, marginTop: 4, opacity: 0.8 }, followButtonWrapper: { marginLeft: 'auto' }, separator: { height: 1, backgroundColor: colors.border, marginLeft: 76 }, emptyContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', paddingHorizontal: 32, paddingTop: 80 }, emptyTitle: { fontSize: 18, fontWeight: '600', color: colors.text, marginTop: 16, textAlign: 'center' }, emptySubtitle: { fontSize: 14, color: colors.secondaryText, marginTop: 8, textAlign: 'center' }, footerLoader: { paddingVertical: 16, alignItems: 'center' } }); var _default = exports.default = UserListScreen; //# sourceMappingURL=UserListScreen.js.map