diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..54b7242 --- /dev/null +++ b/Makefile @@ -0,0 +1,31 @@ +#DEV + + + +build: + docker compose build + +dev: build + docker compose up -d --force-recreate + +devf: dev + docker compose logs -f + +up: + docker compose up -d --force-recreate + +upf: up + docker compose logs -f + +logs: + docker compose logs -f + + +stop: + "!!!!!!!!!!!!!!!!!EXTERMINATUS!!!!!!!!!!!!!!!!!" + docker compose stop +start: + docker compose start + +drop: + docker-compose down --volumes \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index 8a5e571..271ab52 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -20,11 +20,11 @@ services: - "8100:8100" volumes: - .:/app - deploy: - resources: - limits: - cpus: "0.5" - memory: "512M" + # deploy: + # resources: + # limits: + # cpus: "0.5" + # memory: "512M" app2: build: . @@ -36,8 +36,8 @@ services: - "8101:8100" volumes: - .:/app - deploy: - resources: - limits: - cpus: "0.5" - memory: "512M" \ No newline at end of file + # deploy: + # resources: + # limits: + # cpus: "0.5" + # memory: "512M" \ No newline at end of file diff --git a/public/monkey-monkey-with-drone.gif b/public/monkey-monkey-with-drone.gif new file mode 100644 index 0000000..4b38d70 Binary files /dev/null and b/public/monkey-monkey-with-drone.gif differ diff --git a/src/components/CallContainer.css b/src/components/CallContainer.css new file mode 100644 index 0000000..61c82e7 --- /dev/null +++ b/src/components/CallContainer.css @@ -0,0 +1,4 @@ +#full-screen-modal { + --height: 100%; + --width: 100%; +} diff --git a/src/components/CallContainer.tsx b/src/components/CallContainer.tsx index 8a0b1ab..9626bcf 100644 --- a/src/components/CallContainer.tsx +++ b/src/components/CallContainer.tsx @@ -1,16 +1,70 @@ -import React from 'react'; -// import './ExploreContainer.css'; +import React, { useState, useEffect } from 'react'; +import { + IonButton, + IonModal, + IonHeader, + IonToolbar, + IonTitle, + IonContent, +} from '@ionic/react'; +import './CallContainer.css'; +import Peer from 'peerjs'; + +interface InputContainerPerr { + peer: Peer; + isEnable: boolean; +} -// setCalling => bool : true - звонок включен, false - звонок выключен -// const CallContainer: React.FC = () => { - // Растягивался на весь экран - // По середине: кнопки: завершить звонок, вывод сколько времени идет звонок (обнулять при завершении) - // Тебе поможет ionic modal + const [isModalOpen, setIsModalOpen] = useState(false); + const [callTime, setCallTime] = useState(0); + const [callInterval, setCallInterval] = useState(null); + + const handleCallStart = () => { + setIsModalOpen(true); + setCallTime(0); + const interval = setInterval(() => { + setCallTime((prevTime) => prevTime + 1); + }, 1000); + setCallInterval(interval); + }; + + const handleCallEnd = () => { + setIsModalOpen(false); + if (callInterval) { + clearInterval(callInterval); + } + setCallInterval(null); + }; + + useEffect(() => { + // Очистка интервала при размонтировании компонента + return () => { + if (callInterval) { + clearInterval(callInterval); + } + }; + }, [callInterval]); return (
- + Звонок... + + + + + Звонок идет... + + + +
+

Время звонка: {callTime} секунд

+ + Завершить звонок + +
+
+
); }; diff --git a/src/components/ExploreContainer.css b/src/components/ExploreContainer.css index f1a7b31..b92ea26 100644 --- a/src/components/ExploreContainer.css +++ b/src/components/ExploreContainer.css @@ -1,23 +1,88 @@ -#container { -} - -#container strong { - font-size: 20px; - line-height: 26px; -} - -#container p { - font-size: 16px; - line-height: 22px; - color: #8c8c8c; - margin: 0; -} - -#container a { - text-decoration: none; -} - /* ExploreContainer.css */ -.call-button { - margin-left: auto; +#container { + display: flex; + flex-direction: column; + align-items: center; + margin-top: 20px; +} + +.peer-id-container { + display: flex; + align-items: center; + margin-bottom: 20px; +} + +.peer-id-text { + margin-right: 10px; + font-size: 16px; + color: #fff; +} + +.copy-button { + background: none; + border: none; + color: #007aff; + cursor: pointer; + font-size: 16px; +} + +.input-container { + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 20px; + width: 100%; +} + +.call-info-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100%; +} + +.call-info-text { + font-size: 18px; + margin-bottom: 10px; + color: #ffffff; +} + +.call-duration-text { + font-size: 18px; + margin-bottom: 20px; + color: #ffffff; +} + +.hang-up-button { + background: none; + border: none; + color: #ff3b30; /* Красная трубка */ + font-size: 24px; +} + +.call-button { + background: none; + border: none; + color: #007aff; + cursor: pointer; + font-size: 24px; + margin-left: 10px; +} + +.call-button ion-icon { + font-size: 30px; + color: #007aff; +} + +.panel-optimizer { + display: flex; + align-items: center; + justify-content: center; +} + +.drone-gif { + width: 100px; /* Задайте нужный размер */ + height: auto; + margin-bottom: 20px; } diff --git a/src/components/ExploreContainer.tsx b/src/components/ExploreContainer.tsx index 8e28e36..ec229be 100644 --- a/src/components/ExploreContainer.tsx +++ b/src/components/ExploreContainer.tsx @@ -1,8 +1,11 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useCallback } from 'react'; import './ExploreContainer.css'; import InputContainer from './Input'; -import { IonButton, IonContent, IonFooter, IonToolbar, IonPage, IonHeader, IonTitle, IonToast, IonText } from '@ionic/react'; -import Peer from 'peerjs'; +import { + IonButton, IonContent, IonFooter, IonToolbar, IonText, IonModal, IonIcon, IonHeader, IonTitle +} from '@ionic/react'; +import { call as callIcon, close as hangUpIcon } from 'ionicons/icons'; +import Peer, { MediaConnection } from 'peerjs'; const ExploreContainer: React.FC = () => { const [callString, setCallString] = useState(""); @@ -11,58 +14,64 @@ const ExploreContainer: React.FC = () => { const [localStream, setLocalStream] = useState(null); const [connectionInfo, setConnectionInfo] = useState(null); const [isLoading, setIsLoading] = useState(false); + const [isCallActive, setIsCallActive] = useState(false); + const [callDuration, setCallDuration] = useState(0); + const [callInterval, setCallInterval] = useState(null); + const [currentCallId, setCurrentCallId] = useState(null); useEffect(() => { - // Инициализация Peer без сервера, только для локальной сети - const newPeer = new Peer({ - debug: 3, - - }); + const initializePeer = () => { + const newPeer = new Peer({ debug: 3 }); + setPeer(newPeer); - setPeer(newPeer); - - newPeer.on('open', (id) => { - console.log(`Peer успешно открыт с ID: ${id}`); - setConnectionInfo(`Ваш Peer ID: ${id}`); - }); - - newPeer.on('call', (call) => { - console.log('Получен входящий звонок'); - navigator.mediaDevices.getUserMedia({ audio: true }).then((stream) => { - setLocalStream(stream); - call.answer(stream); - call.on('stream', (remoteStream) => { - console.log('Получен удалённый поток'); - playAudio(remoteStream); - }); - }).catch((err) => { - console.error('Не удалось получить локальный поток для ответа', err); + newPeer.on('open', (id) => { + console.log(`Peer успешно открыт с ID: ${id}`); + setConnectionInfo(id); }); - }); - newPeer.on('error', (err) => { - console.error('Ошибка Peer:', err); - }); + newPeer.on('call', (call: MediaConnection) => handleIncomingCall(call)); + newPeer.on('error', (err) => console.error('Ошибка Peer:', err)); - return () => { - if (peer) { - console.log('Уничтожение Peer'); - peer.destroy(); - } + return () => { + newPeer.destroy(); + }; }; + + const cleanup = initializePeer(); + return cleanup; }, []); - const makeCall = () => { + const handleIncomingCall = useCallback((call: MediaConnection) => { + console.log('Получен входящий звонок'); + setCurrentCallId(call.peer); + navigator.mediaDevices.getUserMedia({ audio: true }).then((stream) => { + setLocalStream(stream); + call.answer(stream); + call.on('stream', (remoteStream: MediaStream) => { + console.log('Получен удалённый поток'); + playAudio(remoteStream); + startCallTimer(); + setIsCallActive(true); + }); + }).catch((err) => { + console.error('Не удалось получить локальный поток для ответа', err); + }); + }, []); + + const makeCall = useCallback(() => { if (peer && callString) { setIsLoading(true); console.log(`Попытка звонка на: ${callString}`); navigator.mediaDevices.getUserMedia({ audio: true }).then((stream) => { setLocalStream(stream); const call = peer.call(callString, stream); - call.on('stream', (remoteStream) => { + setCurrentCallId(callString); + call.on('stream', (remoteStream: MediaStream) => { console.log('Получен удалённый поток во время вызова'); playAudio(remoteStream); setIsLoading(false); + startCallTimer(); + setIsCallActive(true); }); }).catch((err) => { console.error('Не удалось получить локальный поток', err); @@ -71,9 +80,29 @@ const ExploreContainer: React.FC = () => { } else { console.warn('Звонок невозможен: отсутствует Peer или callString'); } - }; + }, [peer, callString]); - const playAudio = (stream: MediaStream) => { + const endCall = useCallback(() => { + if (localStream) { + localStream.getTracks().forEach(track => track.stop()); + } + setIsCallActive(false); + setIsEnable(true); + if (callInterval) { + clearInterval(callInterval); + } + setCallDuration(0); + setCurrentCallId(null); + }, [localStream, callInterval]); + + const startCallTimer = useCallback(() => { + const interval = setInterval(() => { + setCallDuration(prevDuration => prevDuration + 1); + }, 1000); + setCallInterval(interval); + }, []); + + const playAudio = useCallback((stream: MediaStream) => { const audioElement = document.createElement('audio'); audioElement.srcObject = stream; audioElement.play().then(() => { @@ -81,38 +110,58 @@ const ExploreContainer: React.FC = () => { }).catch((error) => { console.error('Ошибка воспроизведения аудио', error); }); - }; + }, []); + + const copyPeerId = useCallback(() => { + if (connectionInfo) { + navigator.clipboard.writeText(connectionInfo) + .then(() => { + console.log('Peer ID скопирован в буфер обмена'); + }) + .catch((err) => { + console.error('Не удалось скопировать Peer ID', err); + }); + } + }, [connectionInfo]); return ( <> - - - - DEBUG MODE - - - - {connectionInfo} - {isLoading && ( -
- . - . - . +
+
+ Ваш Peer ID: {connectionInfo} +
- )} +
+ + {/* Модальное окно для звонка */} + + +
+ {/* Добавляем гифку */} + Monkey with Drone + Peer ID: {currentCallId} + {Math.floor(callDuration / 60)}:{callDuration % 60} + + + +
+
+
- + - - - {isLoading ? 'Звонок...' : 'Вызов'} - +
+ + + + +
@@ -120,4 +169,3 @@ const ExploreContainer: React.FC = () => { }; export default ExploreContainer; -