@college-africa/chat-ui-native 1.1.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/cache/actions/summaries.d.ts.map +1 -1
  2. package/dist/cache/actions/summaries.js +6 -7
  3. package/dist/components/ChatApp.d.ts +3 -17
  4. package/dist/components/ChatApp.d.ts.map +1 -1
  5. package/dist/components/ChatApp.js +3 -3
  6. package/dist/components/ConversationItem.d.ts +2 -1
  7. package/dist/components/ConversationItem.d.ts.map +1 -1
  8. package/dist/components/ConversationItem.js +38 -7
  9. package/dist/components/ConversationList.d.ts +2 -1
  10. package/dist/components/ConversationList.d.ts.map +1 -1
  11. package/dist/components/ConversationList.js +79 -14
  12. package/dist/components/Dropdown.d.ts.map +1 -1
  13. package/dist/components/Dropdown.js +1 -1
  14. package/dist/components/MessageBubble.d.ts +2 -1
  15. package/dist/components/MessageBubble.d.ts.map +1 -1
  16. package/dist/components/MessageBubble.js +38 -7
  17. package/dist/components/MessageInput.d.ts.map +1 -1
  18. package/dist/components/MessageInput.js +18 -13
  19. package/dist/components/MessageList.d.ts +3 -3
  20. package/dist/components/MessageList.d.ts.map +1 -1
  21. package/dist/components/MessageList.js +8 -6
  22. package/dist/components/ResponsiveChatLayout.d.ts +18 -0
  23. package/dist/components/ResponsiveChatLayout.d.ts.map +1 -0
  24. package/dist/components/ResponsiveChatLayout.js +161 -0
  25. package/dist/components/SkeletonLoader.d.ts +18 -0
  26. package/dist/components/SkeletonLoader.d.ts.map +1 -0
  27. package/dist/components/SkeletonLoader.js +41 -0
  28. package/dist/utils/sdk.d.ts +2 -3
  29. package/dist/utils/sdk.d.ts.map +1 -1
  30. package/dist/utils/sdk.js +3 -7
  31. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"summaries.d.ts","sourceRoot":"","sources":["../../../src/cache/actions/summaries.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,OAAO,KAAK,EAAE,wBAAwB,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AAKlF,eAAO,MAAM,qBAAqB,GAAI,WAAW,MAAM,GAAG,IAAI,SAE7D,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,cAAc,GAAU,SAAQ,wBAA6B,sEAqBzE,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,GAClC,SAAS,OAAO,EAChB,eAAe,MAAM,SAoCtB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,gBAAgB,GAAI,gBAAgB,MAAM,SAuBtD,CAAC"}
1
+ {"version":3,"file":"summaries.d.ts","sourceRoot":"","sources":["../../../src/cache/actions/summaries.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,OAAO,KAAK,EACV,wBAAwB,EACxB,OAAO,EACR,MAAM,0BAA0B,CAAC;AAKlC,eAAO,MAAM,qBAAqB,GAAI,WAAW,MAAM,GAAG,IAAI,SAE7D,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,cAAc,GAAU,SAAQ,wBAA6B,sEAqBzE,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,GAClC,SAAS,OAAO,EAChB,eAAe,MAAM,SAoCtB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,gBAAgB,GAAI,gBAAgB,MAAM,SAuBtD,CAAC"}
@@ -50,15 +50,14 @@ const updateSummaryForMessage = (message, currentUserId) => {
50
50
  group: message.group,
51
51
  user: otherUserId ?? undefined
52
52
  });
53
- // Do not update unread_message count if conversation is opened
54
- if (summaryId === activeConversationId)
55
- return;
56
53
  const existingSummary = realm.objectForPrimaryKey("Summary", summaryId);
54
+ // Do not update unread_message count if conversation is opened
55
+ const increment = summaryId === activeConversationId ? 0 : 1;
57
56
  if (existingSummary) {
58
57
  // Update existing summary
59
- existingSummary.message_count += 1;
58
+ existingSummary.message_count += increment;
60
59
  existingSummary.last_message_at = message.created_at;
61
- existingSummary.unread_count += 1;
60
+ existingSummary.unread_count += increment;
62
61
  }
63
62
  else {
64
63
  // Create new summary if it doesn't exist
@@ -67,8 +66,8 @@ const updateSummaryForMessage = (message, currentUserId) => {
67
66
  group: message.group ?? undefined,
68
67
  user: otherUserId ?? undefined,
69
68
  last_message_at: message.created_at,
70
- message_count: 1,
71
- unread_count: 1
69
+ message_count: increment,
70
+ unread_count: increment
72
71
  });
73
72
  }
74
73
  };
@@ -5,30 +5,16 @@ import React from "react";
5
5
  import type { ChatClientConfig } from "@college-africa/chat-sdk";
6
6
  import type { StackNavigationOptions } from "@react-navigation/stack";
7
7
  export interface ChatAppProps {
8
- /**
9
- * SDK configuration
10
- */
8
+ /** SDK configuration */
11
9
  config: ChatClientConfig;
12
- /**
13
- * Auth token (optional, can be set later)
14
- */
15
- token?: string;
16
- /**
17
- * Loading component
18
- */
19
10
  loadingComponent?: React.ReactNode;
20
- /**
21
- * Error handler
22
- */
23
11
  onError?: (error: Error) => void;
24
- /**
25
- * Navigation screen options to pass to the stack navigator
26
- */
12
+ /** Navigation screen options to pass to the stack navigator */
27
13
  screenOptions?: StackNavigationOptions;
28
14
  }
29
15
  /**
30
16
  * Main chat application component
31
17
  * Initializes SDK and provides the chat interface
32
18
  */
33
- export declare function ChatApp({ config, token, loadingComponent, onError, screenOptions }: ChatAppProps): React.JSX.Element;
19
+ export declare function ChatApp({ config, loadingComponent, onError, screenOptions }: ChatAppProps): React.JSX.Element;
34
20
  //# sourceMappingURL=ChatApp.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ChatApp.d.ts","sourceRoot":"","sources":["../../src/components/ChatApp.tsx"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAA8B,MAAM,OAAO,CAAC;AAInD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAMjE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAEtE,MAAM,WAAW,YAAY;IAC3B;;OAEG;IACH,MAAM,EAAE,gBAAgB,CAAC;IAEzB;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,gBAAgB,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAEnC;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAEjC;;OAEG;IACH,aAAa,CAAC,EAAE,sBAAsB,CAAC;CACxC;AAED;;;GAGG;AACH,wBAAgB,OAAO,CAAC,EACtB,MAAM,EACN,KAAK,EACL,gBAAgB,EAChB,OAAO,EACP,aAAa,EACd,EAAE,YAAY,qBA2Dd"}
1
+ {"version":3,"file":"ChatApp.d.ts","sourceRoot":"","sources":["../../src/components/ChatApp.tsx"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAA8B,MAAM,OAAO,CAAC;AAInD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAMjE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAEtE,MAAM,WAAW,YAAY;IAC3B,yBAAyB;IACzB,MAAM,EAAE,gBAAgB,CAAC;IACzB,gBAAgB,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACnC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAEjC,+DAA+D;IAC/D,aAAa,CAAC,EAAE,sBAAsB,CAAC;CACxC;AAED;;;GAGG;AACH,wBAAgB,OAAO,CAAC,EACtB,MAAM,EACN,gBAAgB,EAChB,OAAO,EACP,aAAa,EACd,EAAE,YAAY,qBA2Dd"}
@@ -47,7 +47,7 @@ const ChatStack_1 = require("./ChatStack");
47
47
  * Main chat application component
48
48
  * Initializes SDK and provides the chat interface
49
49
  */
50
- function ChatApp({ config, token, loadingComponent, onError, screenOptions }) {
50
+ function ChatApp({ config, loadingComponent, onError, screenOptions }) {
51
51
  const [isReady, setIsReady] = (0, react_1.useState)(false);
52
52
  const [error, setError] = (0, react_1.useState)(null);
53
53
  (0, react_1.useEffect)(() => {
@@ -55,7 +55,7 @@ function ChatApp({ config, token, loadingComponent, onError, screenOptions }) {
55
55
  async function initializeChat() {
56
56
  try {
57
57
  if (!(0, sdk_1.isSDKInitialized)()) {
58
- await (0, sdk_1.initSDK)(config, token);
58
+ await (0, sdk_1.initSDK)(config);
59
59
  }
60
60
  if (mounted) {
61
61
  setIsReady(true);
@@ -73,7 +73,7 @@ function ChatApp({ config, token, loadingComponent, onError, screenOptions }) {
73
73
  return () => {
74
74
  mounted = false;
75
75
  };
76
- }, [config, token, onError]);
76
+ }, [config, onError]);
77
77
  if (error) {
78
78
  return (<react_native_1.View style={styles.centered}>
79
79
  <react_native_paper_1.Text variant="bodyLarge" style={styles.errorText}>
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * ConversationItem - Single conversation list item with unread badge
3
+ * Phase 7: Added React.memo for performance optimization
3
4
  */
4
5
  import React from "react";
5
6
  import type { ConversationItemProps } from "../types";
6
- export declare function ConversationItem({ conversation, onPress }: ConversationItemProps): React.JSX.Element;
7
+ export declare const ConversationItem: React.NamedExoticComponent<ConversationItemProps>;
7
8
  //# sourceMappingURL=ConversationItem.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ConversationItem.d.ts","sourceRoot":"","sources":["../../src/components/ConversationItem.tsx"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAM1B,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAEtD,wBAAgB,gBAAgB,CAAC,EAC/B,YAAY,EACZ,OAAO,EACR,EAAE,qBAAqB,qBA4CvB"}
1
+ {"version":3,"file":"ConversationItem.d.ts","sourceRoot":"","sources":["../../src/components/ConversationItem.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAe,MAAM,OAAO,CAAC;AAMpC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAEtD,eAAO,MAAM,gBAAgB,mDA+C3B,CAAC"}
@@ -1,19 +1,50 @@
1
1
  "use strict";
2
2
  /**
3
3
  * ConversationItem - Single conversation list item with unread badge
4
+ * Phase 7: Added React.memo for performance optimization
4
5
  */
5
- var __importDefault = (this && this.__importDefault) || function (mod) {
6
- return (mod && mod.__esModule) ? mod : { "default": mod };
7
- };
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
19
+ }) : function(o, v) {
20
+ o["default"] = v;
21
+ });
22
+ var __importStar = (this && this.__importStar) || (function () {
23
+ var ownKeys = function(o) {
24
+ ownKeys = Object.getOwnPropertyNames || function (o) {
25
+ var ar = [];
26
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
27
+ return ar;
28
+ };
29
+ return ownKeys(o);
30
+ };
31
+ return function (mod) {
32
+ if (mod && mod.__esModule) return mod;
33
+ var result = {};
34
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
35
+ __setModuleDefault(result, mod);
36
+ return result;
37
+ };
38
+ })();
8
39
  Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.ConversationItem = ConversationItem;
10
- const react_1 = __importDefault(require("react"));
40
+ exports.ConversationItem = void 0;
41
+ const react_1 = __importStar(require("react"));
11
42
  // @ts-ignore - react-native-paper is a peer dependency
12
43
  const react_native_paper_1 = require("react-native-paper");
13
44
  const date_1 = require("../utils/date");
14
45
  const messages_1 = require("../utils/messages");
15
46
  const styles_1 = require("../styles");
16
- function ConversationItem({ conversation, onPress }) {
47
+ exports.ConversationItem = (0, react_1.memo)(function ConversationItem({ conversation, onPress }) {
17
48
  const theme = (0, react_native_paper_1.useTheme)();
18
49
  const styles = useStyles(theme);
19
50
  const initials = (0, messages_1.getInitials)(conversation.name);
@@ -27,7 +58,7 @@ function ConversationItem({ conversation, onPress }) {
27
58
  return (<react_native_paper_1.List.Item title={conversation.name} description={description} onPress={onPress} left={props => (<react_native_paper_1.Avatar.Icon {...props} color={undefined} size={40} icon={conversation.isGroup ? "account-group" : "account-circle"}/>)} right={props => conversation.unread_count > 0 ? (<react_native_paper_1.Badge size={20} style={[styles.badge, { backgroundColor: theme.colors.primary }]}>
28
59
  {conversation.unread_count > 99 ? "99+" : conversation.unread_count}
29
60
  </react_native_paper_1.Badge>) : null} style={styles.item} titleStyle={styles.title} descriptionStyle={styles.description}/>);
30
- }
61
+ });
31
62
  const useStyles = (theme) => {
32
63
  return (0, styles_1.useStyleSheet)({
33
64
  item: {
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * ConversationList - List of all conversations
3
+ * Phase 7: Added skeleton loader, pull-to-refresh, error handling with retry, and React.memo
3
4
  */
4
5
  import React from "react";
5
6
  import type { ConversationListProps } from "../types";
6
- export declare function ConversationList({ onSelectConversation }: ConversationListProps): React.JSX.Element;
7
+ export declare const ConversationList: React.NamedExoticComponent<ConversationListProps>;
7
8
  //# sourceMappingURL=ConversationList.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ConversationList.d.ts","sourceRoot":"","sources":["../../src/components/ConversationList.tsx"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAY1B,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAGtD,wBAAgB,gBAAgB,CAAC,EAC/B,oBAAoB,EACrB,EAAE,qBAAqB,qBAwDvB"}
1
+ {"version":3,"file":"ConversationList.d.ts","sourceRoot":"","sources":["../../src/components/ConversationList.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAe,MAAM,OAAO,CAAC;AAapC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAGtD,eAAO,MAAM,gBAAgB,mDAkG3B,CAAC"}
@@ -1,31 +1,88 @@
1
1
  "use strict";
2
2
  /**
3
3
  * ConversationList - List of all conversations
4
+ * Phase 7: Added skeleton loader, pull-to-refresh, error handling with retry, and React.memo
4
5
  */
5
- var __importDefault = (this && this.__importDefault) || function (mod) {
6
- return (mod && mod.__esModule) ? mod : { "default": mod };
7
- };
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
19
+ }) : function(o, v) {
20
+ o["default"] = v;
21
+ });
22
+ var __importStar = (this && this.__importStar) || (function () {
23
+ var ownKeys = function(o) {
24
+ ownKeys = Object.getOwnPropertyNames || function (o) {
25
+ var ar = [];
26
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
27
+ return ar;
28
+ };
29
+ return ownKeys(o);
30
+ };
31
+ return function (mod) {
32
+ if (mod && mod.__esModule) return mod;
33
+ var result = {};
34
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
35
+ __setModuleDefault(result, mod);
36
+ return result;
37
+ };
38
+ })();
8
39
  Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.ConversationList = ConversationList;
10
- const react_1 = __importDefault(require("react"));
40
+ exports.ConversationList = void 0;
41
+ const react_1 = __importStar(require("react"));
11
42
  const react_native_1 = require("react-native");
12
43
  // @ts-ignore - react-native-paper is a peer dependency
13
44
  const react_native_paper_1 = require("react-native-paper");
14
45
  const ConversationItem_1 = require("./ConversationItem");
46
+ const SkeletonLoader_1 = require("./SkeletonLoader");
15
47
  const useConversationList_1 = require("../hooks/useConversationList");
16
48
  const styles_1 = require("../styles");
17
- function ConversationList({ onSelectConversation }) {
49
+ exports.ConversationList = (0, react_1.memo)(function ConversationList({ onSelectConversation }) {
18
50
  const theme = (0, react_native_paper_1.useTheme)();
19
51
  const styles = useStyles(theme);
20
- const { conversations, loading, error } = (0, useConversationList_1.useConversationList)();
52
+ const { conversations, loading, error, refetch } = (0, useConversationList_1.useConversationList)();
53
+ const [refreshing, setRefreshing] = react_1.default.useState(false);
21
54
  const renderItem = ({ item }) => (<ConversationItem_1.ConversationItem conversation={item} onPress={() => onSelectConversation(item)}/>);
22
55
  const keyExtractor = (item) => item.id;
23
- if (loading) {
24
- return (<react_native_1.View style={styles.centered}>
25
- <react_native_paper_1.ActivityIndicator size="large"/>
26
- </react_native_1.View>);
56
+ const handleRefresh = react_1.default.useCallback(async () => {
57
+ setRefreshing(true);
58
+ try {
59
+ refetch();
60
+ }
61
+ finally {
62
+ setRefreshing(false);
63
+ }
64
+ }, [refetch]);
65
+ const handleRetry = react_1.default.useCallback(async () => {
66
+ try {
67
+ // Try to reconnect the SDK if it's disconnected
68
+ const { getSDK } = await Promise.resolve().then(() => __importStar(require("../utils/sdk")));
69
+ const sdk = getSDK();
70
+ if (!sdk.ws.isConnected()) {
71
+ await sdk.connect();
72
+ }
73
+ }
74
+ catch (err) {
75
+ console.error("Failed to reconnect SDK:", err);
76
+ }
77
+ // Refetch conversations after reconnection attempt
78
+ refetch();
79
+ }, [refetch]);
80
+ // Initial loading state - show skeleton
81
+ if (loading && conversations.length === 0) {
82
+ return <SkeletonLoader_1.SkeletonLoader count={5} height={72}/>;
27
83
  }
28
- if (error) {
84
+ // Error state - show error message with retry button
85
+ if (error && conversations.length === 0) {
29
86
  return (<react_native_1.View style={styles.centered}>
30
87
  <react_native_paper_1.Text variant="bodyMedium" style={styles.errorText}>
31
88
  Failed to load conversations
@@ -33,8 +90,12 @@ function ConversationList({ onSelectConversation }) {
33
90
  <react_native_paper_1.Text variant="bodySmall" style={styles.errorMessage}>
34
91
  {error.message}
35
92
  </react_native_paper_1.Text>
93
+ <react_native_paper_1.Button mode="contained" onPress={handleRetry} style={styles.retryButton} icon="refresh">
94
+ Retry
95
+ </react_native_paper_1.Button>
36
96
  </react_native_1.View>);
37
97
  }
98
+ // Empty state
38
99
  if (conversations.length === 0) {
39
100
  return (<react_native_1.View style={styles.centered}>
40
101
  <react_native_paper_1.Text variant="bodyMedium" style={styles.emptyText}>
@@ -45,8 +106,8 @@ function ConversationList({ onSelectConversation }) {
45
106
  </react_native_paper_1.Text>
46
107
  </react_native_1.View>);
47
108
  }
48
- return (<react_native_1.FlatList data={conversations} renderItem={renderItem} keyExtractor={keyExtractor} style={styles.list}/>);
49
- }
109
+ return (<react_native_1.FlatList data={conversations} renderItem={renderItem} keyExtractor={keyExtractor} style={styles.list} refreshControl={<react_native_1.RefreshControl refreshing={refreshing} onRefresh={handleRefresh} tintColor={theme.colors.primary} colors={[theme.colors.primary]}/>}/>);
110
+ });
50
111
  const useStyles = (theme) => {
51
112
  return (0, styles_1.useStyleSheet)({
52
113
  list: {
@@ -66,9 +127,13 @@ const useStyles = (theme) => {
66
127
  color: theme.colors.error
67
128
  },
68
129
  errorMessage: {
130
+ marginBottom: 16,
69
131
  color: theme.colors.errorContainer,
70
132
  textAlign: "center"
71
133
  },
134
+ retryButton: {
135
+ marginTop: 8
136
+ },
72
137
  emptyText: {
73
138
  marginBottom: 8,
74
139
  color: theme.colors.errorContainer
@@ -1 +1 @@
1
- {"version":3,"file":"Dropdown.d.ts","sourceRoot":"","sources":["../../src/components/Dropdown.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAmB,MAAM,OAAO,CAAC;AAMxC,MAAM,WAAW,aAAa;IAC5B;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,iBAAiB,GAAG,UAAU,CAAC;IAC7D;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB;AAED,wBAAgB,QAAQ,CAAC,EACvB,KAAK,EACL,QAAQ,EACR,QAAgB,EAChB,IAAa,EACb,WAAW,EACX,gBAAgB,EAChB,SAAS,EACV,EAAE,aAAa,qBAiDf"}
1
+ {"version":3,"file":"Dropdown.d.ts","sourceRoot":"","sources":["../../src/components/Dropdown.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAmB,MAAM,OAAO,CAAC;AAMxC,MAAM,WAAW,aAAa;IAC5B;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,iBAAiB,GAAG,UAAU,CAAC;IAC7D;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB;AAED,wBAAgB,QAAQ,CAAC,EACvB,KAAK,EACL,QAAQ,EACR,QAAgB,EAChB,IAAa,EACb,WAAW,EACX,gBAAgB,EAChB,SAAS,EACV,EAAE,aAAa,qBAgDf"}
@@ -55,7 +55,7 @@ function Dropdown({ label, children, disabled = false, mode = "text", buttonStyl
55
55
  setVisible(false);
56
56
  onDismiss?.();
57
57
  };
58
- return (<react_native_paper_1.Menu visible={visible} onDismiss={handleDismiss} anchorPosition="top" style={{ marginTop: -16 }} anchor={<react_native_paper_1.Button mode={mode} onPress={handleToggle} disabled={disabled} icon="menu-down" contentStyle={styles.buttonContent} style={[styles.button, buttonStyle]}>
58
+ return (<react_native_paper_1.Menu visible={visible} onDismiss={handleDismiss} anchorPosition="top" anchor={<react_native_paper_1.Button mode={mode} onPress={handleToggle} disabled={disabled} icon="menu-down" contentStyle={styles.buttonContent} style={[styles.button, buttonStyle]}>
59
59
  {label}
60
60
  </react_native_paper_1.Button>} contentStyle={[styles.menuContent, menuContentStyle]}>
61
61
  {react_1.default.Children.map(children, child => {
@@ -1,5 +1,6 @@
1
1
  /**
2
2
  * MessageBubble - Individual message display component
3
+ * Phase 7: Added React.memo for performance optimization
3
4
  */
4
5
  import React from "react";
5
6
  import type { MessageBubbleProps } from "../types";
@@ -14,5 +15,5 @@ export interface EnhancedMessageBubbleProps extends MessageBubbleProps {
14
15
  /** Is last message in group */
15
16
  isLastInGroup?: boolean;
16
17
  }
17
- export declare function MessageBubble({ message, isOwn, showSender, status, showAvatar, isFirstInGroup, isLastInGroup }: EnhancedMessageBubbleProps): React.JSX.Element;
18
+ export declare const MessageBubble: React.NamedExoticComponent<EnhancedMessageBubbleProps>;
18
19
  //# sourceMappingURL=MessageBubble.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"MessageBubble.d.ts","sourceRoot":"","sources":["../../src/components/MessageBubble.tsx"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAQ1B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AACnD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAG9D,MAAM,WAAW,0BAA2B,SAAQ,kBAAkB;IACpE,6CAA6C;IAC7C,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,kBAAkB;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,gCAAgC;IAChC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,+BAA+B;IAC/B,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EACP,KAAK,EACL,UAAU,EACV,MAAM,EACN,UAAU,EACV,cAAsB,EACtB,aAAqB,EACtB,EAAE,0BAA0B,qBA8E5B"}
1
+ {"version":3,"file":"MessageBubble.d.ts","sourceRoot":"","sources":["../../src/components/MessageBubble.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAe,MAAM,OAAO,CAAC;AAQpC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AACnD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAG9D,MAAM,WAAW,0BAA2B,SAAQ,kBAAkB;IACpE,6CAA6C;IAC7C,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,kBAAkB;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,gCAAgC;IAChC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,+BAA+B;IAC/B,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,eAAO,MAAM,aAAa,wDAsFxB,CAAC"}
@@ -1,13 +1,44 @@
1
1
  "use strict";
2
2
  /**
3
3
  * MessageBubble - Individual message display component
4
+ * Phase 7: Added React.memo for performance optimization
4
5
  */
5
- var __importDefault = (this && this.__importDefault) || function (mod) {
6
- return (mod && mod.__esModule) ? mod : { "default": mod };
7
- };
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
19
+ }) : function(o, v) {
20
+ o["default"] = v;
21
+ });
22
+ var __importStar = (this && this.__importStar) || (function () {
23
+ var ownKeys = function(o) {
24
+ ownKeys = Object.getOwnPropertyNames || function (o) {
25
+ var ar = [];
26
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
27
+ return ar;
28
+ };
29
+ return ownKeys(o);
30
+ };
31
+ return function (mod) {
32
+ if (mod && mod.__esModule) return mod;
33
+ var result = {};
34
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
35
+ __setModuleDefault(result, mod);
36
+ return result;
37
+ };
38
+ })();
8
39
  Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.MessageBubble = MessageBubble;
10
- const react_1 = __importDefault(require("react"));
40
+ exports.MessageBubble = void 0;
41
+ const react_1 = __importStar(require("react"));
11
42
  const react_native_1 = require("react-native");
12
43
  // @ts-ignore - react-native-paper is a peer dependency
13
44
  const react_native_paper_1 = require("react-native-paper");
@@ -16,7 +47,7 @@ const messages_1 = require("../utils/messages");
16
47
  const styles_1 = require("../styles");
17
48
  const MessageStatus_1 = require("./MessageStatus");
18
49
  const misc_1 = require("../utils/misc");
19
- function MessageBubble({ message, isOwn, showSender, status, showAvatar, isFirstInGroup = false, isLastInGroup = false }) {
50
+ exports.MessageBubble = (0, react_1.memo)(function MessageBubble({ message, isOwn, showSender, status, showAvatar, isFirstInGroup = false, isLastInGroup = false }) {
20
51
  const theme = (0, react_native_paper_1.useTheme)();
21
52
  const styles = useStyles(theme);
22
53
  return (<react_native_1.View style={[
@@ -71,7 +102,7 @@ function MessageBubble({ message, isOwn, showSender, status, showAvatar, isFirst
71
102
  </react_native_1.View>
72
103
  </react_native_1.View>
73
104
  </react_native_1.View>);
74
- }
105
+ });
75
106
  const useStyles = (theme) => {
76
107
  return (0, styles_1.useStyleSheet)({
77
108
  container: {
@@ -1 +1 @@
1
- {"version":3,"file":"MessageInput.d.ts","sourceRoot":"","sources":["../../src/components/MessageInput.tsx"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAmB,MAAM,OAAO,CAAC;AAKxC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAElD,wBAAgB,YAAY,CAAC,EAAE,aAAa,EAAE,QAAQ,EAAE,EAAE,iBAAiB,qBA2C1E"}
1
+ {"version":3,"file":"MessageInput.d.ts","sourceRoot":"","sources":["../../src/components/MessageInput.tsx"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAmB,MAAM,OAAO,CAAC;AAKxC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAElD,wBAAgB,YAAY,CAAC,EAAE,aAAa,EAAE,QAAQ,EAAE,EAAE,iBAAiB,qBAyC1E"}
@@ -54,7 +54,9 @@ function MessageInput({ onSendMessage, disabled }) {
54
54
  }
55
55
  };
56
56
  return (<react_native_1.View style={[styles.container, { backgroundColor: theme.colors.surface }]}>
57
- <react_native_paper_1.TextInput value={text} onChangeText={setText} placeholder="Type a message..." mode="outlined" multiline maxLength={1000} disabled={disabled} outlineColor={theme.colors.surfaceVariant} style={styles.input} outlineStyle={styles.inputOutline} contentStyle={styles.inputContent} onSubmitEditing={handleSend} blurOnSubmit={false}/>
57
+ <react_native_1.View style={styles.inputWrapper}>
58
+ <react_native_1.TextInput value={text} onChangeText={setText} placeholder="Type a message..." multiline maxLength={1000} readOnly={disabled} style={styles.input} onSubmitEditing={handleSend} cursorColor={theme.colors.primary}/>
59
+ </react_native_1.View>
58
60
  <react_native_paper_1.IconButton icon="send" mode="contained" onPress={handleSend} disabled={disabled || !text.trim()} style={styles.sendButton} iconColor={theme.colors.onPrimary} containerColor={text.trim() ? theme.colors.primary : theme.colors.surfaceDisabled}/>
59
61
  </react_native_1.View>);
60
62
  }
@@ -63,23 +65,26 @@ const useStyles = (theme) => {
63
65
  container: {
64
66
  flexDirection: "row",
65
67
  alignItems: "flex-end",
66
- paddingHorizontal: 8,
67
- paddingVertical: 8
68
+ padding: 8,
69
+ gap: 8
68
70
  },
69
- input: {
71
+ inputWrapper: {
70
72
  flex: 1,
71
- maxHeight: 100,
72
- marginRight: 8
73
- },
74
- inputOutline: {
75
- borderRadius: 28
73
+ minHeight: 40,
74
+ overflow: "scroll",
75
+ padding: 10,
76
+ borderWidth: 1,
77
+ borderRadius: 20,
78
+ borderColor: theme.colors.onSurfaceDisabled,
79
+ justifyContent: "center"
76
80
  },
77
- inputContent: {
78
- paddingHorizontal: 16,
79
- height: 10
81
+ input: {
82
+ overflow: "scroll",
83
+ padding: 0,
84
+ maxHeight: 100
80
85
  },
81
86
  sendButton: {
82
- marginBottom: 4
87
+ margin: 0
83
88
  }
84
89
  });
85
90
  };
@@ -1,8 +1,8 @@
1
1
  /**
2
- * MessageList - Scrollable list of messages using inverted FlatList
3
- * Phase 7: Inverted list with pagination for older messages
2
+ * MessageList - Scrollable list of messages using FlashList
3
+ * Phase 7: Added React.memo for performance optimization and improved FlashList usage
4
4
  */
5
5
  import React from "react";
6
6
  import type { MessageListProps } from "../types";
7
- export declare function MessageList({ messages, isLoading, isLoadingMore, hasOlderMessages, currentUserId, onLoadMore }: MessageListProps): React.JSX.Element;
7
+ export declare const MessageList: React.NamedExoticComponent<MessageListProps>;
8
8
  //# sourceMappingURL=MessageList.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../src/components/MessageList.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAyC,MAAM,OAAO,CAAC;AAY9D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAIjD,wBAAgB,WAAW,CAAC,EAC1B,QAAQ,EACR,SAAS,EACT,aAAa,EACb,gBAAgB,EAChB,aAAa,EACb,UAAU,EACX,EAAE,gBAAgB,qBA6GlB"}
1
+ {"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../src/components/MessageList.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAA+C,MAAM,OAAO,CAAC;AAYpE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAIjD,eAAO,MAAM,WAAW,8CAsHtB,CAAC"}
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  /**
3
- * MessageList - Scrollable list of messages using inverted FlatList
4
- * Phase 7: Inverted list with pagination for older messages
3
+ * MessageList - Scrollable list of messages using FlashList
4
+ * Phase 7: Added React.memo for performance optimization and improved FlashList usage
5
5
  */
6
6
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
7
  if (k2 === undefined) k2 = k;
@@ -37,7 +37,7 @@ var __importStar = (this && this.__importStar) || (function () {
37
37
  };
38
38
  })();
39
39
  Object.defineProperty(exports, "__esModule", { value: true });
40
- exports.MessageList = MessageList;
40
+ exports.MessageList = void 0;
41
41
  const react_1 = __importStar(require("react"));
42
42
  const react_native_1 = require("react-native");
43
43
  // @ts-ignore - react-native-paper is a peer dependency
@@ -46,7 +46,7 @@ const MessageBubble_1 = require("./MessageBubble");
46
46
  const messages_1 = require("../utils/messages");
47
47
  const styles_1 = require("../styles");
48
48
  const flash_list_1 = require("@shopify/flash-list");
49
- function MessageList({ messages, isLoading, isLoadingMore, hasOlderMessages, currentUserId, onLoadMore }) {
49
+ exports.MessageList = (0, react_1.memo)(function MessageList({ messages, isLoading, isLoadingMore, hasOlderMessages, currentUserId, onLoadMore }) {
50
50
  const styles = useStyles();
51
51
  const flatListRef = (0, react_1.useRef)(null);
52
52
  const isUserAtBottomRef = (0, react_1.useRef)(true);
@@ -103,8 +103,10 @@ function MessageList({ messages, isLoading, isLoadingMore, hasOlderMessages, cur
103
103
  </react_native_paper_1.Text>
104
104
  </react_native_1.View>);
105
105
  }
106
- return (<flash_list_1.FlashList ref={flatListRef} data={messages} renderItem={renderMessage} keyExtractor={keyExtractor} contentContainerStyle={styles.contentContainer} onStartReached={hasOlderMessages ? handleLoadMore : undefined} onStartReachedThreshold={0.5} onScroll={onScroll} scrollEventThrottle={16} ListHeaderComponent={renderLoadingMore}/>);
107
- }
106
+ return (<flash_list_1.FlashList ref={flatListRef} data={messages} renderItem={renderMessage} keyExtractor={keyExtractor} contentContainerStyle={styles.contentContainer} onStartReached={hasOlderMessages ? handleLoadMore : undefined} onStartReachedThreshold={0.5} onScroll={onScroll} scrollEventThrottle={16} ListHeaderComponent={renderLoadingMore}
107
+ // Performance optimizations
108
+ removeClippedSubviews={true}/>);
109
+ });
108
110
  const useStyles = () => {
109
111
  const theme = (0, react_native_paper_1.useTheme)();
110
112
  return (0, styles_1.useStyleSheet)({
@@ -0,0 +1,18 @@
1
+ /**
2
+ * ResponsiveChatLayout - Tablet split-view layout for conversation list and chat
3
+ * Phase 7: Responsive layouts with side-by-side view on tablets
4
+ */
5
+ import React from "react";
6
+ import type { ConversationItem } from "../types";
7
+ interface ResponsiveChatLayoutProps {
8
+ /** Callback when a conversation is selected (for mobile) */
9
+ onSelectConversation?: (conversation: ConversationItem) => void;
10
+ }
11
+ /**
12
+ * Responsive layout that shows:
13
+ * - Mobile: Only conversation list (chat navigates to new screen)
14
+ * - Tablet (md+): Split view with conversation list (1/3) and chat (2/3)
15
+ */
16
+ export declare function ResponsiveChatLayout({ onSelectConversation }: ResponsiveChatLayoutProps): React.JSX.Element;
17
+ export {};
18
+ //# sourceMappingURL=ResponsiveChatLayout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ResponsiveChatLayout.d.ts","sourceRoot":"","sources":["../../src/components/ResponsiveChatLayout.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAgC,MAAM,OAAO,CAAC;AAUrD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAKjD,UAAU,yBAAyB;IACjC,4DAA4D;IAC5D,oBAAoB,CAAC,EAAE,CAAC,YAAY,EAAE,gBAAgB,KAAK,IAAI,CAAC;CACjE;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,oBAAoB,EACrB,EAAE,yBAAyB,qBAgF3B"}
@@ -0,0 +1,161 @@
1
+ "use strict";
2
+ /**
3
+ * ResponsiveChatLayout - Tablet split-view layout for conversation list and chat
4
+ * Phase 7: Responsive layouts with side-by-side view on tablets
5
+ */
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
19
+ }) : function(o, v) {
20
+ o["default"] = v;
21
+ });
22
+ var __importStar = (this && this.__importStar) || (function () {
23
+ var ownKeys = function(o) {
24
+ ownKeys = Object.getOwnPropertyNames || function (o) {
25
+ var ar = [];
26
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
27
+ return ar;
28
+ };
29
+ return ownKeys(o);
30
+ };
31
+ return function (mod) {
32
+ if (mod && mod.__esModule) return mod;
33
+ var result = {};
34
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
35
+ __setModuleDefault(result, mod);
36
+ return result;
37
+ };
38
+ })();
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ exports.ResponsiveChatLayout = ResponsiveChatLayout;
41
+ const react_1 = __importStar(require("react"));
42
+ const react_native_1 = require("react-native");
43
+ // @ts-ignore - react-navigation is a peer dependency
44
+ const native_1 = require("@react-navigation/native");
45
+ const ConversationList_1 = require("./ConversationList");
46
+ const ChatView_1 = require("./ChatView");
47
+ const styles_1 = require("../styles");
48
+ const users_1 = require("../cache/actions/users");
49
+ // @ts-ignore - react-native-paper is a peer dependency
50
+ const react_native_paper_1 = require("react-native-paper");
51
+ /**
52
+ * Responsive layout that shows:
53
+ * - Mobile: Only conversation list (chat navigates to new screen)
54
+ * - Tablet (md+): Split view with conversation list (1/3) and chat (2/3)
55
+ */
56
+ function ResponsiveChatLayout({ onSelectConversation }) {
57
+ const isTablet = (0, styles_1.useIsMinWidth)("md");
58
+ const styles = useStyles();
59
+ // @ts-ignore - navigation type inference
60
+ const navigation = (0, native_1.useNavigation)();
61
+ const [selectedConversationId, setSelectedConversationId] = (0, react_1.useState)(null);
62
+ const [selectedConversationName, setSelectedConversationName] = (0, react_1.useState)(undefined);
63
+ const currentUser = (0, users_1.getCurrentUser)();
64
+ const isAdmin = ["admin", "system"].includes(currentUser?.role) || undefined;
65
+ const handleSelectConversation = (0, react_1.useCallback)((conversation) => {
66
+ setSelectedConversationId(conversation.id);
67
+ setSelectedConversationName(conversation.name);
68
+ // On mobile, navigate to the Chat screen
69
+ if (!isTablet && onSelectConversation) {
70
+ onSelectConversation(conversation);
71
+ }
72
+ }, [isTablet, onSelectConversation]);
73
+ // Tablet layout: side-by-side
74
+ if (isTablet) {
75
+ return (<react_native_1.View style={styles.container}>
76
+ {/* Conversation List - Left Side (1/3) */}
77
+ <react_native_1.View style={styles.conversationListContainer}>
78
+ <react_native_1.View style={styles.conversationListHeader}>
79
+ {/* @ts-ignore - navigation type inference */}
80
+ <react_native_paper_1.IconButton icon="account-multiple-plus" size={20} onPress={() => navigation.navigate("CreateGroup")} disabled={!isAdmin}/>
81
+ </react_native_1.View>
82
+ <ConversationList_1.ConversationList onSelectConversation={handleSelectConversation}/>
83
+ </react_native_1.View>
84
+
85
+ {/* Divider */}
86
+ <react_native_paper_1.Divider style={styles.divider}/>
87
+
88
+ {/* Chat View - Right Side (2/3) */}
89
+ <react_native_1.View style={styles.chatContainer}>
90
+ {selectedConversationId ? (<>
91
+ <react_native_1.View style={styles.chatHeader}>
92
+ <react_native_paper_1.IconButton icon="close" size={20} onPress={() => setSelectedConversationId(null)}/>
93
+ </react_native_1.View>
94
+ <ChatView_1.ChatView conversationId={selectedConversationId}/>
95
+ </>) : (<react_native_1.View style={styles.emptyChat}>
96
+ {/* @ts-ignore - react-native-paper is a peer dependency */}
97
+ {/* eslint-disable-next-line react-native/no-inline-styles */}
98
+ <react_native_paper_1.IconButton icon="message-outline" size={64} style={{ opacity: 0.3 }}/>
99
+ </react_native_1.View>)}
100
+ </react_native_1.View>
101
+ </react_native_1.View>);
102
+ }
103
+ // Mobile layout: only conversation list (chat navigates via stack)
104
+ return (<react_native_1.View style={styles.container}>
105
+ <ConversationList_1.ConversationList onSelectConversation={handleSelectConversation}/>
106
+ </react_native_1.View>);
107
+ }
108
+ const useStyles = () => {
109
+ return (0, styles_1.useStyleSheet)({
110
+ container: {
111
+ flex: 1,
112
+ flexDirection: "row",
113
+ backgroundColor: "transparent"
114
+ },
115
+ conversationListContainer: {
116
+ width: "33%",
117
+ borderRightWidth: 1
118
+ },
119
+ conversationListHeader: {
120
+ flexDirection: "row",
121
+ justifyContent: "flex-end",
122
+ padding: 8,
123
+ borderBottomWidth: 1
124
+ },
125
+ divider: {
126
+ width: 1
127
+ },
128
+ chatContainer: {
129
+ flex: 1,
130
+ backgroundColor: "transparent"
131
+ },
132
+ chatHeader: {
133
+ flexDirection: "row",
134
+ justifyContent: "flex-end",
135
+ padding: 8,
136
+ borderBottomWidth: 1
137
+ },
138
+ emptyChat: {
139
+ flex: 1,
140
+ justifyContent: "center",
141
+ alignItems: "center"
142
+ }
143
+ }, {
144
+ // Mobile styles
145
+ [styles_1.breakpoints.down("md")]: {
146
+ container: {
147
+ flexDirection: "column"
148
+ },
149
+ conversationListContainer: {
150
+ width: "100%",
151
+ borderRightWidth: 0
152
+ },
153
+ conversationListHeader: {
154
+ display: "none" // Hide on mobile, use screen header instead
155
+ },
156
+ divider: {
157
+ display: "none"
158
+ }
159
+ }
160
+ });
161
+ };
@@ -0,0 +1,18 @@
1
+ /**
2
+ * SkeletonLoader - Loading skeleton component for lists
3
+ * Phase 7: Skeleton loaders for better loading states
4
+ */
5
+ import React from "react";
6
+ export interface SkeletonLoaderProps {
7
+ /** Number of items to render */
8
+ count?: number;
9
+ /** Height of each skeleton item */
10
+ height?: number;
11
+ /** Width of skeleton (default: 100%) */
12
+ width?: number | "100%";
13
+ }
14
+ /**
15
+ * Skeleton loader for displaying placeholders while content loads
16
+ */
17
+ export declare function SkeletonLoader({ count, height, width }: SkeletonLoaderProps): React.JSX.Element;
18
+ //# sourceMappingURL=SkeletonLoader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SkeletonLoader.d.ts","sourceRoot":"","sources":["../../src/components/SkeletonLoader.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAM1B,MAAM,WAAW,mBAAmB;IAClC,gCAAgC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mCAAmC;IACnC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wCAAwC;IACxC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CACzB;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,EAC7B,KAAS,EACT,MAAW,EACX,KAAc,EACf,EAAE,mBAAmB,qBAqBrB"}
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ /**
3
+ * SkeletonLoader - Loading skeleton component for lists
4
+ * Phase 7: Skeleton loaders for better loading states
5
+ */
6
+ var __importDefault = (this && this.__importDefault) || function (mod) {
7
+ return (mod && mod.__esModule) ? mod : { "default": mod };
8
+ };
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.SkeletonLoader = SkeletonLoader;
11
+ const react_1 = __importDefault(require("react"));
12
+ const react_native_1 = require("react-native");
13
+ // @ts-ignore - react-native-paper is a peer dependency
14
+ const react_native_paper_1 = require("react-native-paper");
15
+ const styles_1 = require("../styles");
16
+ /**
17
+ * Skeleton loader for displaying placeholders while content loads
18
+ */
19
+ function SkeletonLoader({ count = 5, height = 72, width = "100%" }) {
20
+ const styles = useStyles();
21
+ const theme = (0, react_native_paper_1.useTheme)();
22
+ return (<>
23
+ {Array.from({ length: count }).map((_, index) => (<react_native_1.View key={index} style={[
24
+ styles.skeleton,
25
+ {
26
+ height,
27
+ width,
28
+ backgroundColor: theme.colors.surfaceVariant
29
+ }
30
+ ]}/>))}
31
+ </>);
32
+ }
33
+ const useStyles = () => {
34
+ return (0, styles_1.useStyleSheet)({
35
+ skeleton: {
36
+ marginHorizontal: 16,
37
+ marginVertical: 8,
38
+ borderRadius: 8
39
+ }
40
+ });
41
+ };
@@ -5,12 +5,11 @@ import { ChatSDK } from "@college-africa/chat-sdk";
5
5
  import type { ChatClientConfig } from "@college-africa/chat-sdk";
6
6
  /**
7
7
  * Initialize the SDK singleton
8
- * @param config - Chat client configuration
9
- * @param token - Authentication token (optional, can be set later)
8
+ * @param config - Chat client configuration (contains authToken and/or getAuthToken)
10
9
  * @returns Promise that resolves with the initialized SDK instance
11
10
  * @throws Error if SDK is already initialized
12
11
  */
13
- export declare function initSDK(config: ChatClientConfig, token?: string): Promise<ChatSDK>;
12
+ export declare function initSDK(config: ChatClientConfig): Promise<ChatSDK>;
14
13
  /**
15
14
  * Get the current SDK instance
16
15
  * @returns The SDK instance
@@ -1 +1 @@
1
- {"version":3,"file":"sdk.d.ts","sourceRoot":"","sources":["../../src/utils/sdk.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AACnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAMjE;;;;;;GAMG;AACH,wBAAsB,OAAO,CAC3B,MAAM,EAAE,gBAAgB,EACxB,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,OAAO,CAAC,CAmClB;AAED;;;;GAIG;AACH,wBAAgB,MAAM,IAAI,OAAO,CAKhC;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,IAAI,CAKpC;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CAE1C"}
1
+ {"version":3,"file":"sdk.d.ts","sourceRoot":"","sources":["../../src/utils/sdk.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AACnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAMjE;;;;;GAKG;AACH,wBAAsB,OAAO,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC,CA+BxE;AAED;;;;GAIG;AACH,wBAAgB,MAAM,IAAI,OAAO,CAKhC;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,IAAI,CAKpC;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CAE1C"}
package/dist/utils/sdk.js CHANGED
@@ -13,12 +13,11 @@ const users_1 = require("../cache/actions/users");
13
13
  let sdkInstance = null;
14
14
  /**
15
15
  * Initialize the SDK singleton
16
- * @param config - Chat client configuration
17
- * @param token - Authentication token (optional, can be set later)
16
+ * @param config - Chat client configuration (contains authToken and/or getAuthToken)
18
17
  * @returns Promise that resolves with the initialized SDK instance
19
18
  * @throws Error if SDK is already initialized
20
19
  */
21
- async function initSDK(config, token) {
20
+ async function initSDK(config) {
22
21
  if (sdkInstance)
23
22
  return sdkInstance;
24
23
  // Create SDK instance with React Native's WebSocket
@@ -26,13 +25,10 @@ async function initSDK(config, token) {
26
25
  ...config,
27
26
  wsImplementation: WebSocket
28
27
  });
29
- // Set auth token if provided
30
- if (token) {
31
- await sdkInstance.setAuthToken(token);
32
- }
33
28
  // Connect WebSocket
34
29
  await sdkInstance.connect();
35
30
  // Fetch and cache current user data
31
+ // requireAuthToken will be called by getMe(), which handles token fetch/renewal
36
32
  const me = await sdkInstance.getMe();
37
33
  (0, users_1.setCurrentUser)({
38
34
  id: me.id,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@college-africa/chat-ui-native",
3
- "version": "1.1.2",
3
+ "version": "1.2.0",
4
4
  "description": "React Native chat UI library for College Africa",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -36,7 +36,7 @@
36
36
  "event-target-polyfill": "^0.0.4",
37
37
  "formik": "^2.4.5",
38
38
  "realm": "^12.0.0",
39
- "@college-africa/chat-sdk": "1.1.2",
39
+ "@college-africa/chat-sdk": "1.2.0",
40
40
  "yup": "^1.3.3"
41
41
  },
42
42
  "devDependencies": {