set chat message overlay draggable
This commit is contained in:
parent
da11561f47
commit
739c019404
@ -70,6 +70,11 @@ export function PhoneSimulator({
|
||||
const pushToTalkButtonRef = useRef<HTMLButtonElement>(null);
|
||||
const [showChatOverlay, setShowChatOverlay] = useState(false);
|
||||
const [chatOverlayPosition, setChatOverlayPosition] = useState({ x: 0, y: 0 }); // Will be positioned at top-right by ChatOverlay component
|
||||
const [chatTogglePosition, setChatTogglePosition] = useState({ x: 0, y: 56 }); // Initial position aligned with visualizer
|
||||
const [isDraggingChatToggle, setIsDraggingChatToggle] = useState(false);
|
||||
const chatToggleRef = useRef<HTMLButtonElement>(null);
|
||||
const chatToggleDragOffset = useRef({ x: 0, y: 0 });
|
||||
const chatToggleHasDragged = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
const voiceAttr = config.settings.attributes?.find(a => a.key === "voice");
|
||||
@ -79,30 +84,45 @@ export function PhoneSimulator({
|
||||
}, [config.settings.attributes]);
|
||||
|
||||
// Set talking_mode attribute when connected or when mode changes
|
||||
const lastTalkingModeRef = useRef<string | null>(null);
|
||||
const configAttributesRef = useRef(config.settings.attributes);
|
||||
|
||||
// Update config attributes ref when it changes
|
||||
useEffect(() => {
|
||||
configAttributesRef.current = config.settings.attributes;
|
||||
}, [config.settings.attributes]);
|
||||
|
||||
useEffect(() => {
|
||||
if (roomState === ConnectionState.Connected && localParticipant) {
|
||||
const talkingMode = isPushToTalkMode ? "push_to_talk" : "realtime";
|
||||
|
||||
// Only update if the mode actually changed
|
||||
if (lastTalkingModeRef.current === talkingMode) {
|
||||
return;
|
||||
}
|
||||
lastTalkingModeRef.current = talkingMode;
|
||||
|
||||
try {
|
||||
// Get current attributes to preserve them
|
||||
const currentAttributes: Record<string, string> = {};
|
||||
// Note: LiveKit's setAttributes replaces all attributes, so we need to merge
|
||||
// with existing ones from config if any
|
||||
const configAttributes = config.settings.attributes || [];
|
||||
// Get current attributes from config to preserve them
|
||||
const attributesToSet: Record<string, string> = {};
|
||||
const configAttributes = configAttributesRef.current || [];
|
||||
configAttributes.forEach(attr => {
|
||||
if (attr.key && attr.value) {
|
||||
currentAttributes[attr.key] = attr.value;
|
||||
attributesToSet[attr.key] = attr.value;
|
||||
}
|
||||
});
|
||||
// Set talking_mode along with other attributes
|
||||
localParticipant.setAttributes({
|
||||
...currentAttributes,
|
||||
talking_mode: talkingMode,
|
||||
});
|
||||
// Add talking_mode
|
||||
attributesToSet.talking_mode = talkingMode;
|
||||
|
||||
localParticipant.setAttributes(attributesToSet);
|
||||
} catch (error) {
|
||||
console.error("Failed to set talking_mode attribute:", error);
|
||||
}
|
||||
} else if (roomState === ConnectionState.Disconnected) {
|
||||
// Reset ref when disconnected
|
||||
lastTalkingModeRef.current = null;
|
||||
}
|
||||
}, [roomState, localParticipant, isPushToTalkMode, config.settings.attributes]);
|
||||
}, [roomState, localParticipant, isPushToTalkMode]);
|
||||
|
||||
const [currentTime, setCurrentTime] = useState("");
|
||||
|
||||
@ -174,6 +194,100 @@ export function PhoneSimulator({
|
||||
};
|
||||
}, [isDragging]);
|
||||
|
||||
// Chat toggle button drag handlers
|
||||
const handleChatToggleDragStart = (e: React.MouseEvent | React.TouchEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation(); // Prevent triggering the button click
|
||||
setIsDraggingChatToggle(true);
|
||||
chatToggleHasDragged.current = false;
|
||||
if (!phoneContainerRef.current || !chatToggleRef.current) return;
|
||||
|
||||
const containerRect = phoneContainerRef.current.getBoundingClientRect();
|
||||
const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX;
|
||||
const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY;
|
||||
|
||||
// Calculate offset relative to container
|
||||
chatToggleDragOffset.current = {
|
||||
x: clientX - containerRect.left - chatTogglePosition.x,
|
||||
y: clientY - containerRect.top - chatTogglePosition.y,
|
||||
};
|
||||
};
|
||||
|
||||
const handleChatToggleDragMove = (e: MouseEvent | TouchEvent) => {
|
||||
if (!isDraggingChatToggle || !phoneContainerRef.current || !chatToggleRef.current) return;
|
||||
|
||||
e.preventDefault();
|
||||
chatToggleHasDragged.current = true; // Mark that we've actually dragged
|
||||
|
||||
const containerRect = phoneContainerRef.current.getBoundingClientRect();
|
||||
const buttonRect = chatToggleRef.current.getBoundingClientRect();
|
||||
|
||||
const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX;
|
||||
const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY;
|
||||
|
||||
// Calculate new position relative to container
|
||||
let newX = clientX - containerRect.left - chatToggleDragOffset.current.x;
|
||||
let newY = clientY - containerRect.top - chatToggleDragOffset.current.y;
|
||||
|
||||
// Constrain within container
|
||||
const maxX = containerRect.width - buttonRect.width;
|
||||
const maxY = containerRect.height - buttonRect.height;
|
||||
// On mobile (width < 768px), status bar is hidden, so allow dragging to top (y=0)
|
||||
// On desktop, keep status bar height constraint (48px)
|
||||
const isMobile = typeof window !== 'undefined' && window.innerWidth < 768;
|
||||
const minY = isMobile ? 0 : 48; // statusBarHeight = 48px
|
||||
|
||||
newX = Math.max(0, Math.min(newX, maxX));
|
||||
newY = Math.max(minY, Math.min(newY, maxY));
|
||||
|
||||
setChatTogglePosition({
|
||||
x: newX,
|
||||
y: newY,
|
||||
});
|
||||
};
|
||||
|
||||
const handleChatToggleDragEnd = () => {
|
||||
setIsDraggingChatToggle(false);
|
||||
// Reset the flag after a short delay to allow onClick to check it
|
||||
setTimeout(() => {
|
||||
chatToggleHasDragged.current = false;
|
||||
}, 100);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isDraggingChatToggle) {
|
||||
window.addEventListener("mouseup", handleChatToggleDragEnd);
|
||||
window.addEventListener("mousemove", handleChatToggleDragMove);
|
||||
window.addEventListener("touchend", handleChatToggleDragEnd);
|
||||
window.addEventListener("touchmove", handleChatToggleDragMove, { passive: false });
|
||||
}
|
||||
return () => {
|
||||
window.removeEventListener("mouseup", handleChatToggleDragEnd);
|
||||
window.removeEventListener("mousemove", handleChatToggleDragMove);
|
||||
window.removeEventListener("touchend", handleChatToggleDragEnd);
|
||||
window.removeEventListener("touchmove", handleChatToggleDragMove);
|
||||
};
|
||||
}, [isDraggingChatToggle]);
|
||||
|
||||
// Initialize chat toggle button position when connected and container is available
|
||||
useEffect(() => {
|
||||
if (roomState === ConnectionState.Connected && phoneContainerRef.current) {
|
||||
// Use a small delay to ensure the button is rendered
|
||||
const timer = setTimeout(() => {
|
||||
if (phoneContainerRef.current && chatToggleRef.current) {
|
||||
const containerWidth = phoneContainerRef.current.offsetWidth;
|
||||
const buttonWidth = chatToggleRef.current.offsetWidth || 44; // Approximate button width
|
||||
// Position at rightmost border (flush with right edge)
|
||||
setChatTogglePosition({
|
||||
x: containerWidth - buttonWidth - 56,
|
||||
y: 56,
|
||||
});
|
||||
}
|
||||
}, 100);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [roomState]); // Initialize when connected
|
||||
|
||||
useEffect(() => {
|
||||
if (showCameraMenu) {
|
||||
Room.getLocalDevices("videoinput").then(setCameras);
|
||||
@ -877,22 +991,30 @@ export function PhoneSimulator({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Chat Toggle Button - Top Right, aligned with audio visualizer */}
|
||||
{/* Chat Toggle Button - Top Right, aligned with audio visualizer (Draggable) */}
|
||||
{roomState === ConnectionState.Connected &&
|
||||
voiceAssistant.agent &&
|
||||
phoneMode !== "important_message" &&
|
||||
phoneMode !== "capture" && (
|
||||
<button
|
||||
className={`absolute right-2 z-50 p-3 rounded-full backdrop-blur-md transition-colors shadow-lg ${
|
||||
ref={chatToggleRef}
|
||||
className={`absolute z-50 p-3 rounded-full backdrop-blur-md transition-colors shadow-lg cursor-move select-none touch-none ${
|
||||
showChatOverlay
|
||||
? "bg-blue-500/80 text-white"
|
||||
: "bg-gray-800/70 text-white hover:bg-gray-800/90"
|
||||
}`}
|
||||
onClick={() => setShowChatOverlay(!showChatOverlay)}
|
||||
title={showChatOverlay ? "Hide chat" : "Show chat"}
|
||||
onClick={(e) => {
|
||||
// Only toggle if we didn't just drag
|
||||
if (!chatToggleHasDragged.current) {
|
||||
setShowChatOverlay(!showChatOverlay);
|
||||
}
|
||||
}}
|
||||
onMouseDown={handleChatToggleDragStart}
|
||||
onTouchStart={handleChatToggleDragStart}
|
||||
title={showChatOverlay ? "Hide chat (drag to move)" : "Show chat (drag to move)"}
|
||||
style={{
|
||||
top: '56px', // Align with audio visualizer initial position
|
||||
right: '8px',
|
||||
left: chatTogglePosition.x,
|
||||
top: chatTogglePosition.y,
|
||||
}}
|
||||
>
|
||||
<ChatIcon className="w-5 h-5 md:w-6 md:h-6" />
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user