Skip to content

Commit

Permalink
[feat/#58] 사이드 네비게이션 및 재촬영 관련 로직 수정, 코드 최적화
Browse files Browse the repository at this point in the history
  • Loading branch information
G-hoon committed Sep 23, 2024
1 parent a7cd920 commit 83b01e5
Show file tree
Hide file tree
Showing 15 changed files with 309 additions and 135 deletions.
12 changes: 12 additions & 0 deletions src/assets/icons/posture-snapshot-retake-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
55 changes: 55 additions & 0 deletions src/components/Modal/GoodPostureGuideModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import CloseCrewPanelIcon from "@assets/images/posture-snapshot-guide.png"
import { ReactElement } from "react"
import ModalContainer from "../ModalContainer"

const GoodPostureGuidePopupModal = ({ onClose }: { onClose: () => void }): ReactElement => {
return (
<ModalContainer onClose={onClose}>
<div className="absolute inset-0 flex items-center justify-center bg-zinc-900/25">
{/* blur 처리 */}
<div className="flex h-[472px] w-[800px] flex-col items-center rounded-lg bg-white pt-10 shadow-lg">
<div className="pb-6">
<div className="text-[30px] font-bold text-[#1E2535]">바른 자세 가이드</div>
</div>
{/* content */}
<div className="flex items-center gap-8 pb-6">
<div className="flex h-[254px] w-[284px] flex-col items-center justify-end rounded-[17px] bg-[#EFEFF0]">
<img src={CloseCrewPanelIcon} alt="스냅샷 가이드" />
</div>
<div className="flex flex-col gap-8">
<div className="flex gap-3">
<span className="flex h-6 w-6 justify-center rounded-full bg-[#5A9CFF] text-center font-semibold text-white">
1
</span>
<span className="font-[20px] font-semibold text-zinc-800">머리와 목을 일직선으로 곧게 펴기</span>
</div>
<div className="flex gap-3">
<span className="flex h-6 w-6 justify-center rounded-full bg-[#5A9CFF] text-center font-semibold text-white">
2
</span>
<span className="font-[20px] font-semibold text-zinc-800">양쪽 어깨 일직선 유지하기</span>
</div>
<div className="flex gap-3">
<span className="flex h-6 w-6 justify-center rounded-full bg-[#5A9CFF] text-center font-semibold text-white">
3
</span>
<span className="font-[20px] font-semibold text-zinc-800">팔은 책상 위에 수평으로 두기</span>
</div>
<div className="flex gap-3">
<span className="flex h-6 w-6 justify-center rounded-full bg-[#5A9CFF] text-center font-semibold text-white">
4
</span>
<span className="font-[20px] font-semibold text-zinc-800">등과 허리는 등받이에 지지하기</span>
</div>
</div>
</div>
<button className="w-[354px] rounded-full bg-[#1A75FF] py-3 text-white" onClick={onClose}>
확인
</button>
</div>
</div>
</ModalContainer>
)
}

export default GoodPostureGuidePopupModal
6 changes: 4 additions & 2 deletions src/components/Modal/Modals.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import { ModalsDispatchContext, ModalsStateContext } from "@/contexts/ModalsContext"
import { useContext, useEffect } from "react"
import { useLocation } from "react-router-dom"
import CreateCrewModal from "./CreateCrewModal"
import GoodPostureGuidePopupModal from "./GoodPostureGuideModal"
import InviteCrewModal from "./InviteCrewModal"
import JoinCrewModal from "./JoinCrewModal"
import WithdrawCrewModal from "./WithdrawCrewModal"
import ToWithdrawModal from "./ToWithdrawModal"
import { useLocation } from "react-router-dom"
import WithdrawCrewModal from "./WithdrawCrewModal"

export const modals = {
createCrewModal: CreateCrewModal,
inviteCrewModal: InviteCrewModal,
joinCrewModal: JoinCrewModal,
withdrawCrewModal: WithdrawCrewModal,
ToWithdrawModal: ToWithdrawModal,
postureGuideModal: GoodPostureGuidePopupModal,
}

const Modals = (): React.ReactNode => {
Expand Down
110 changes: 48 additions & 62 deletions src/components/PoseDetector.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
import { position } from "@/api"
import { duration } from "@/api/notification"
import { poseType } from "@/api/pose"
import { useCameraPermission } from "@/hooks/useCameraPermission"
import { useGuidePopup } from "@/hooks/useGuidePopup"
import { useSendPose } from "@/hooks/usePoseMutation"
import usePushNotification from "@/hooks/usePushNotification"
import { useCreateSnaphot } from "@/hooks/useSnapshotMutation"
import { useNotificationStore } from "@/store/NotificationStore"
import { useSnapshotStore } from "@/store/SnapShotStore"
import type { pose } from "@/utils/detector"
import { detectHandOnChin, detectSlope, detectTextNeck, detectTailboneSit } from "@/utils/detector"
import { detectHandOnChin, detectSlope, detectTailboneSit, detectTextNeck } from "@/utils/detector"
import { drawPose } from "@/utils/drawer"
import { worker } from "@/utils/worker"
import { useCallback, useEffect, useRef, useState } from "react"
import { useLocation } from "react-router-dom"
import Camera from "./Camera"
import GuidePopup from "./Posture/GuidePopup/GuidePopup"
import { useSnapshotStore } from "@/store/SnapshotStore"
import { useCreateSnaphot } from "@/hooks/useSnapshotMutation"
import { position } from "@/api"
import { useSendPose } from "@/hooks/usePoseMutation"
import { poseType } from "@/api/pose"
import PostureMessage from "./Posture/PostureMessage"
import Controls from "./Posture/Controls"
import { useNotificationStore } from "@/store/NotificationStore"
import { duration } from "@/api/notification"
import { useCameraPermission } from "@/hooks/useCameraPermission"
import { useLocation } from "react-router-dom"
import GuidePopupModal from "./Posture/GuidePopup/GuidePopupModal"
import PostureMessage from "./Posture/PostureMessage"

const PoseDetector: React.FC = () => {
const [isScriptLoaded, setIsScriptLoaded] = useState<boolean>(false)
Expand All @@ -26,10 +27,12 @@ const PoseDetector: React.FC = () => {
const [isTailboneSit, setIsTailboneSit] = useState<boolean | null>(null)
const [isHandOnChin, setIsHandOnChin] = useState<boolean | null>(null)
const [isModelLoaded, setIsModelLoaded] = useState<boolean>(false)
const [isSnapSaved, setIsSnapSaved] = useState<boolean>(false)
const [isPopupVisible, setIsPopupVisible] = useState<boolean>(true)
// const [isSnapShotSaved, setIsSnapSaved] = useState<boolean>(false)

const { showNotification } = usePushNotification()

const { isPopupOpen, handleClosePopup, openPopup } = useGuidePopup()

const modelRef = useRef<any>(null)
const snapRef = useRef<pose[] | null>(null)
const resultRef = useRef<pose[] | null>(null)
Expand All @@ -46,11 +49,10 @@ const PoseDetector: React.FC = () => {

const canvasRef = useRef<HTMLCanvasElement>(null)

const snapshot = useSnapshotStore((state) => state.snapshot)
const { isSnapShotSaved, snapshot, setSnapShot } = useSnapshotStore()
const createSnapMutation = useCreateSnaphot()
const sendPoseMutation = useSendPose()

const setSnap = useSnapshotStore((state) => state.setSnapshot)
const userNoti = useNotificationStore((state) => state.notification)

const { requestNotificationPermission } = usePushNotification()
Expand Down Expand Up @@ -115,11 +117,11 @@ const PoseDetector: React.FC = () => {
condition: boolean | null,
timerRef: React.MutableRefObject<any>,
poseType: poseType,
isSnapSaved: boolean,
isSnapShotSaved: boolean,
cntRef: React.MutableRefObject<any>,
isShowNoti: boolean | undefined
): void => {
if (condition && isSnapSaved) {
if (condition && isSnapShotSaved) {
if (!timerRef.current) {
console.log(poseType, "start")
timerRef.current = setInterval(() => {
Expand Down Expand Up @@ -158,24 +160,24 @@ const PoseDetector: React.FC = () => {
if (_isTailboneSit !== null) setIsTailboneSit(_isTailboneSit)

// 공통 타이머 관리 함수 호출
managePoseTimer(_isTextNeck, turtleNeckTimer, "TURTLE_NECK", isSnapSaved, turtleNeckCnt, _isShowNoti)
managePoseTimer(_isTextNeck, turtleNeckTimer, "TURTLE_NECK", isSnapShotSaved, turtleNeckCnt, _isShowNoti)
managePoseTimer(
_isShoulderTwist,
shoulderTwistTimer,
"SHOULDER_TWIST",
isSnapSaved,
isSnapShotSaved,
shoulderTwistCnt,
_isShowNoti
)
managePoseTimer(_isTailboneSit, tailboneSitTimer, "TAILBONE_SIT", isSnapSaved, tailboneSitCnt, _isShowNoti)
managePoseTimer(_isHandOnChin, chinUtpTimer, "CHIN_UTP", isSnapSaved, chinUtpCnt, _isShowNoti)
managePoseTimer(_isTailboneSit, tailboneSitTimer, "TAILBONE_SIT", isSnapShotSaved, tailboneSitCnt, _isShowNoti)
managePoseTimer(_isHandOnChin, chinUtpTimer, "CHIN_UTP", isSnapShotSaved, chinUtpCnt, _isShowNoti)
const isRight = !_isTextNeck && !_isHandOnChin && !_isShoulderTwist && !_isTailboneSit
if (canvasRef.current) drawPose(results, canvasRef.current, isRight)
} else {
if (canvasRef.current) drawPose(results, canvasRef.current)
}
},
[setIsShoulderTwist, setIsTextNeck, setIsHandOnChin, setIsTailboneSit, isSnapSaved, managePoseTimer, userNoti]
[setIsShoulderTwist, setIsTextNeck, setIsHandOnChin, setIsTailboneSit, isSnapShotSaved, managePoseTimer, userNoti]
)

const detectStart = useCallback(
Expand Down Expand Up @@ -204,21 +206,21 @@ const PoseDetector: React.FC = () => {
{
onSuccess: () => {
if (snapRef.current) {
setSnap(snapRef.current[0].keypoints)
setIsSnapSaved(true)
setSnapShot(snapRef.current[0].keypoints)
// setIsSnapSaved(true)
}
},
}
)
}
}
}
}, [createSnapMutation, snapshot, setSnap])
}, [createSnapMutation, snapshot, setSnapShot])

const getUserSnap = (): void => {
if (snapshot) {
snapRef.current = [{ keypoints: snapshot }]
setIsSnapSaved(true)
// setIsSnapSaved(true)
}
}

Expand All @@ -235,15 +237,6 @@ const PoseDetector: React.FC = () => {
notificationTimer.current = null
}

const clearSnap = (): void => {
if (snapshot) {
snapRef.current = null
setIsSnapSaved(false)
setSnap(null)
clearTimers() // 타이머들을 초기화
}
}

const clearCnt = (): void => {
turtleNeckCnt.current = 0
shoulderTwistCnt.current = 0
Expand Down Expand Up @@ -293,11 +286,11 @@ const PoseDetector: React.FC = () => {
}, [])

useEffect(() => {
if (!isSnapSaved || !hasPermission) {
if (!isSnapShotSaved || !hasPermission) {
clearTimers() // 스냅샷이 저장되지 않았을 때 타이머들을 초기화
clearCnt() // 횟수도 초기화
}
}, [isSnapSaved, hasPermission])
}, [isSnapShotSaved, hasPermission])

useEffect(() => {
if (isModelLoaded && hasPermission) {
Expand All @@ -313,7 +306,7 @@ const PoseDetector: React.FC = () => {
}, [snapshot])

useEffect(() => {
if (!isSnapSaved || !userNoti) return
if (!isSnapShotSaved || !userNoti) return

clearCnt()
clearInterval(notificationTimer.current)
Expand All @@ -328,16 +321,11 @@ const PoseDetector: React.FC = () => {
}
}, 1000 * 60 * t)
}
}, [userNoti, isSnapSaved])
}, [userNoti, isSnapShotSaved])

// 팝업 열기
const handleShowPopup = (): void => {
setIsPopupVisible(true)
}

// 팝업 닫기
const handleClosePopup = (): void => {
setIsPopupVisible(false)
openPopup()
}

return (
Expand All @@ -351,24 +339,22 @@ const PoseDetector: React.FC = () => {
<Camera detectStart={detectStart} canvasRef={canvasRef} />
{isModelLoaded && (
<>
<PostureMessage
isSnapSaved={isSnapSaved}
isShoulderTwist={isShoulderTwist}
isTextNeck={isTextNeck}
isHandOnChin={isHandOnChin}
isTailboneSit={isTailboneSit}
hasPermission={hasPermission}
/>
<Controls
isSnapSaved={isSnapSaved}
getInitSnap={getInitSnap}
clearSnap={clearSnap}
handleShowPopup={handleShowPopup}
hasPermission={hasPermission}
/>
{!isPopupOpen && (
<PostureMessage
isSnapShotSaved={isSnapShotSaved}
isShoulderTwist={isShoulderTwist}
isTextNeck={isTextNeck}
isHandOnChin={isHandOnChin}
isTailboneSit={isTailboneSit}
hasPermission={hasPermission}
/>
)}
{!isSnapShotSaved && hasPermission && (
<Controls getInitSnap={getInitSnap} handleShowPopup={handleShowPopup} />
)}
</>
)}
{isPopupVisible && <GuidePopup onClose={handleClosePopup} />}
{isPopupOpen && <GuidePopupModal onClose={handleClosePopup} />}
</div>
)}
</>
Expand Down
57 changes: 20 additions & 37 deletions src/components/Posture/Controls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,46 +2,29 @@ import PostureCheckIcon from "@assets/icons/good-posture-check-button-icon.svg?r
import GuideIcon from "@assets/icons/posture-guide-button-icon.svg?react"

const Controls: React.FC<{
isSnapSaved: boolean
hasPermission: boolean
getInitSnap: () => void
clearSnap: () => void
handleShowPopup: () => void
}> = ({ isSnapSaved, getInitSnap, clearSnap, handleShowPopup, hasPermission }) => {
return !hasPermission ? null : (
}> = ({ getInitSnap, handleShowPopup }) => {
return (
<div className="absolute bottom-0 flex w-full items-center justify-center gap-[16px] p-[50px] text-white">
{!isSnapSaved ? (
<>
<button
className="flex w-[260px] items-center justify-center rounded rounded-full bg-white bg-opacity-80 px-10 py-3 font-semibold leading-[32px] text-zinc-900"
onClick={handleShowPopup}
>
<div className="flex flex-row items-center gap-2">
<GuideIcon />
<span>가이드 다시 볼게요!</span>
</div>
</button>
<button
className="flex w-[260px] items-center justify-center rounded rounded-full bg-[#1A75FF] bg-opacity-80 px-10 py-3 font-semibold leading-[32px] text-white"
onClick={getInitSnap}
>
<div className="flex flex-row items-center gap-2">
<PostureCheckIcon />
바른자세를 취했어요!
</div>
</button>
</>
) : (
<button
className="flex w-[260px] items-center justify-center rounded rounded-full bg-[#1A75FF] bg-opacity-80 p-[20px] text-white"
onClick={clearSnap}
>
<div className="flex flex-row items-center gap-2">
<PostureCheckIcon />
스냅샷 다시찍기
</div>
</button>
)}
<button
className="flex w-[260px] items-center justify-center rounded rounded-full bg-white bg-opacity-80 px-10 py-3 font-semibold leading-[32px] text-zinc-900"
onClick={handleShowPopup}
>
<div className="flex flex-row items-center gap-2">
<GuideIcon />
<span>가이드 다시 볼게요!</span>
</div>
</button>
<button
className="flex w-[260px] items-center justify-center rounded rounded-full bg-[#1A75FF] bg-opacity-80 px-10 py-3 font-semibold leading-[32px] text-white"
onClick={getInitSnap}
>
<div className="flex flex-row items-center gap-2">
<PostureCheckIcon />
바른자세를 취했어요!
</div>
</button>
</div>
)
}
Expand Down
Loading

0 comments on commit 83b01e5

Please sign in to comment.