@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.
- package/dist/cache/actions/summaries.d.ts.map +1 -1
- package/dist/cache/actions/summaries.js +6 -7
- package/dist/components/ChatApp.d.ts +3 -17
- package/dist/components/ChatApp.d.ts.map +1 -1
- package/dist/components/ChatApp.js +3 -3
- package/dist/components/ConversationItem.d.ts +2 -1
- package/dist/components/ConversationItem.d.ts.map +1 -1
- package/dist/components/ConversationItem.js +38 -7
- package/dist/components/ConversationList.d.ts +2 -1
- package/dist/components/ConversationList.d.ts.map +1 -1
- package/dist/components/ConversationList.js +79 -14
- package/dist/components/Dropdown.d.ts.map +1 -1
- package/dist/components/Dropdown.js +1 -1
- package/dist/components/MessageBubble.d.ts +2 -1
- package/dist/components/MessageBubble.d.ts.map +1 -1
- package/dist/components/MessageBubble.js +38 -7
- package/dist/components/MessageInput.d.ts.map +1 -1
- package/dist/components/MessageInput.js +18 -13
- package/dist/components/MessageList.d.ts +3 -3
- package/dist/components/MessageList.d.ts.map +1 -1
- package/dist/components/MessageList.js +8 -6
- package/dist/components/ResponsiveChatLayout.d.ts +18 -0
- package/dist/components/ResponsiveChatLayout.d.ts.map +1 -0
- package/dist/components/ResponsiveChatLayout.js +161 -0
- package/dist/components/SkeletonLoader.d.ts +18 -0
- package/dist/components/SkeletonLoader.d.ts.map +1 -0
- package/dist/components/SkeletonLoader.js +41 -0
- package/dist/utils/sdk.d.ts +2 -3
- package/dist/utils/sdk.d.ts.map +1 -1
- package/dist/utils/sdk.js +3 -7
- 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,
|
|
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 +=
|
|
58
|
+
existingSummary.message_count += increment;
|
|
60
59
|
existingSummary.last_message_at = message.created_at;
|
|
61
|
-
existingSummary.unread_count +=
|
|
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:
|
|
71
|
-
unread_count:
|
|
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,
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
6
|
-
|
|
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 =
|
|
10
|
-
const react_1 =
|
|
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
|
|
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
|
|
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
|
|
6
|
-
|
|
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 =
|
|
10
|
-
const react_1 =
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
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,
|
|
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"
|
|
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
|
|
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
|
|
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
|
|
6
|
-
|
|
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 =
|
|
10
|
-
const react_1 =
|
|
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,
|
|
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
|
-
<
|
|
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
|
-
|
|
67
|
-
|
|
68
|
+
padding: 8,
|
|
69
|
+
gap: 8
|
|
68
70
|
},
|
|
69
|
-
|
|
71
|
+
inputWrapper: {
|
|
70
72
|
flex: 1,
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
borderRadius:
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
81
|
+
input: {
|
|
82
|
+
overflow: "scroll",
|
|
83
|
+
padding: 0,
|
|
84
|
+
maxHeight: 100
|
|
80
85
|
},
|
|
81
86
|
sendButton: {
|
|
82
|
-
|
|
87
|
+
margin: 0
|
|
83
88
|
}
|
|
84
89
|
});
|
|
85
90
|
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* MessageList - Scrollable list of messages using
|
|
3
|
-
* Phase 7:
|
|
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
|
|
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,
|
|
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
|
|
4
|
-
* Phase 7:
|
|
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 =
|
|
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
|
+
};
|
package/dist/utils/sdk.d.ts
CHANGED
|
@@ -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
|
|
12
|
+
export declare function initSDK(config: ChatClientConfig): Promise<ChatSDK>;
|
|
14
13
|
/**
|
|
15
14
|
* Get the current SDK instance
|
|
16
15
|
* @returns The SDK instance
|
package/dist/utils/sdk.d.ts.map
CHANGED
|
@@ -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
|
|
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
|
|
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.
|
|
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.
|
|
39
|
+
"@college-africa/chat-sdk": "1.2.0",
|
|
40
40
|
"yup": "^1.3.3"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|