Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Backend/febird-api/src/exercise/exercise.controller.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Controller, Get, Param } from '@nestjs/common';
99999999import { Controller, Get, Param } from '@nestjs/common';
import { ExerciseService } from './exercise.service';

@Controller('exercise')
Expand Down
2 changes: 1 addition & 1 deletion Backend/febird-api/src/exercise/exercise.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ export class Exercise {

@OneToMany(() => Routine, (routine) => routine.exercise)
routines: Routine[];
}
}
Binary file added ColorChipsData.zip
Binary file not shown.
14 changes: 7 additions & 7 deletions Frontend-iOS/FebirdApp/FebirdApp.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,14 @@
8F0F48062C63723F004E3B86 /* RoutineViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F0F48052C63723F004E3B86 /* RoutineViewModel.swift */; };
8F0F48082C637261004E3B86 /* LevelViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F0F48072C637261004E3B86 /* LevelViewModel.swift */; };
8F0F480A2C6372EA004E3B86 /* ExerciseViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F0F48092C6372EA004E3B86 /* ExerciseViewModel.swift */; };
8F0F48132C637905004E3B86 /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = 8F0F48122C637905004E3B86 /* Alamofire */; };
8F0F48152C63795A004E3B86 /* HistoryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F0F48142C63795A004E3B86 /* HistoryViewModel.swift */; };
8F0F48172C637985004E3B86 /* MemberViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F0F48162C637985004E3B86 /* MemberViewModel.swift */; };
8F8A77E72C5E7AF9008E61D7 /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F8A77E62C5E7AF9008E61D7 /* SettingsViewModel.swift */; };
8F8A77EA2C5E7CE1008E61D7 /* CellConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F8A77E92C5E7CE1008E61D7 /* CellConfiguration.swift */; };
8F8A77EC2C5F554F008E61D7 /* UserProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F8A77EB2C5F554F008E61D7 /* UserProfile.swift */; };
8F8A77EE2C5F56BD008E61D7 /* ProfileSettingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F8A77ED2C5F56BD008E61D7 /* ProfileSettingViewModel.swift */; };
8F8A77F02C5F734B008E61D7 /* SettingHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F8A77EF2C5F734B008E61D7 /* SettingHeaderView.swift */; };
8F910D7E2CB7926600FCDB6A /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = 8F910D7D2CB7926600FCDB6A /* Alamofire */; };
8F92DF7A2C4B93730071F336 /* ExerciseGuideView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F92DF792C4B93730071F336 /* ExerciseGuideView.swift */; };
8F92DF7C2C4B9A390071F336 /* CustomNavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F92DF7B2C4B9A390071F336 /* CustomNavigationBar.swift */; };
8F92DF7E2C4BA9750071F336 /* ExerciseGuideListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F92DF7D2C4BA9750071F336 /* ExerciseGuideListView.swift */; };
Expand Down Expand Up @@ -341,7 +341,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
8F0F48132C637905004E3B86 /* Alamofire in Frameworks */,
8F910D7E2CB7926600FCDB6A /* Alamofire in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -918,7 +918,7 @@
);
name = FebirdApp;
packageProductDependencies = (
8F0F48122C637905004E3B86 /* Alamofire */,
8F910D7D2CB7926600FCDB6A /* Alamofire */,
);
productName = FebirdApp;
productReference = 3EF6F0492C36937E00EEF18F /* FebirdApp.app */;
Expand Down Expand Up @@ -994,7 +994,7 @@
mainGroup = 3EF6F0402C36937D00EEF18F;
packageReferences = (
8F0F48102C6375A9004E3B86 /* XCRemoteSwiftPackageReference "SwiftLintPlugins" */,
8F0F48112C637905004E3B86 /* XCRemoteSwiftPackageReference "Alamofire" */,
8F910D7C2CB7926600FCDB6A /* XCRemoteSwiftPackageReference "Alamofire" */,
);
productRefGroup = 3EF6F04A2C36937E00EEF18F /* Products */;
projectDirPath = "";
Expand Down Expand Up @@ -1561,7 +1561,7 @@
minimumVersion = 0.56.1;
};
};
8F0F48112C637905004E3B86 /* XCRemoteSwiftPackageReference "Alamofire" */ = {
8F910D7C2CB7926600FCDB6A /* XCRemoteSwiftPackageReference "Alamofire" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/Alamofire/Alamofire.git";
requirement = {
Expand All @@ -1572,9 +1572,9 @@
/* End XCRemoteSwiftPackageReference section */

/* Begin XCSwiftPackageProductDependency section */
8F0F48122C637905004E3B86 /* Alamofire */ = {
8F910D7D2CB7926600FCDB6A /* Alamofire */ = {
isa = XCSwiftPackageProductDependency;
package = 8F0F48112C637905004E3B86 /* XCRemoteSwiftPackageReference "Alamofire" */;
package = 8F910D7C2CB7926600FCDB6A /* XCRemoteSwiftPackageReference "Alamofire" */;
productName = Alamofire;
};
/* End XCSwiftPackageProductDependency section */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,35 +9,42 @@ import SwiftUI
import Vision
import AVFoundation

/**
- Vision: 머신 비전 프레임워크로, 이미지 분석 및 비디오 처리에 사용할 수 있습니다. 이 코드는 사람의 신체 포즈를 인식하는 데 사용됩니다.
- AVFoundation: 비디오와 오디오 캡처 및 처리를 위한 프레임워크입니다. 카메라 세션을 설정하고 비디오 데이터를 캡처합니다.
- Combine: @Published 속성을 사용하여 데이터의 변화를 관찰하고, UI와의 바인딩을 쉽게 관리합니다.
*/

class ExerciseDetector: NSObject, ObservableObject {
@Published var currentExercise: ExerciseType = .overheadClap
@Published var count: Int = 0
@Published var cameraPermissionStatus: AVAuthorizationStatus = .notDetermined
@Published var isDetecting: Bool = false
@Published var currentJoints: [VNHumanBodyPoseObservation.JointName] = []
@Published var lastObservation: VNHumanBodyPoseObservation?
@Published var currentExercise: ExerciseType = .overheadClap // 현재 운동 종류
@Published var count: Int = 0 // 운동 동작 카운트
@Published var cameraPermissionStatus: AVAuthorizationStatus = .notDetermined // 카메라 권한 상태
@Published var isDetecting: Bool = false // 현재 동작 인식 여부
@Published var currentJoints: [VNHumanBodyPoseObservation.JointName] = [] // 현재 인식된 관절 목록
@Published var lastObservation: VNHumanBodyPoseObservation? // 마지막 관절 관측 결과

var captureSession: AVCaptureSession?
var videoDataOutput: AVCaptureVideoDataOutput?
var previewLayer: AVCaptureVideoPreviewLayer?
var captureSession: AVCaptureSession? // 비디오 캡처 세션
var videoDataOutput: AVCaptureVideoDataOutput? // 비디오 데이터 출력
var previewLayer: AVCaptureVideoPreviewLayer? // 비디오 미리보기 레이어

private var lastPlayedSound: SystemSoundID?
private var lastPlayedSound: SystemSoundID? // 마지막으로 재생된 소리의 ID

var exerciseStateString: String {
switch currentExercise {
case .overheadClap:
return clappingState.rawValue
return clappingState.rawValue // 현재 운동 상태 문자열 반환
case .downwardPunch:
return downwardPunchState.rawValue
case .sumoSquat:
return sumoSquatState.rawValue
}
}

var clappingState: ClappingState = .start
var downwardPunchState: DownwardPunchState = .standing
var sumoSquatState: SumoSquatState = .standing
var clappingState: ClappingState = .start // 박수 운동 상태
var downwardPunchState: DownwardPunchState = .standing // 아래로 주먹치기 운동 상태
var sumoSquatState: SumoSquatState = .standing // 스모 스쿼트 운동 상태

/// 운동 상태를 초기화하는 메서드
func resetExerciseState() {
switch currentExercise {
case .overheadClap:
Expand All @@ -49,29 +56,32 @@ class ExerciseDetector: NSObject, ObservableObject {
}
}

/// 동작 인식을 시작하거나 중지하는 메서드
func toggleDetection() {
isDetecting.toggle()
isDetecting.toggle() // 인식 상태 전환
if isDetecting {
startDetecting()
startDetecting() // 인식 시작
} else {
stopDetecting()
stopDetecting() // 인식 중지
}
}

/// 동작 인식을 시작하는 메서드
func startDetecting() {
isDetecting = true
isDetecting = true // 인식 상태 설정
if captureSession == nil {
setupCaptureSession()
setupCaptureSession() // 캡처 세션 설정
}
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
self?.captureSession?.startRunning()
self?.captureSession?.startRunning() // 비디오 캡처 세션 시작
}
}

/// 동작 인식을 중지하는 메서드
func stopDetecting() {
isDetecting = false
isDetecting = false // 인식 상태 해제
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
self?.captureSession?.stopRunning()
self?.captureSession?.stopRunning() // 비디오 캡처 세션 중지
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ class ExerciseGuideViewModel: ObservableObject {
"팔을 천천히 내리면서 시작 자세로 돌아옵니다."
]

/// 비디오를 로드하여 재생합니다.
func loadVideo() {
guard let url = URL(string: "https://strfeo.blob.core.windows.net/exercise-video/466512^Overhead_Clap^Shoulders.mp4") else { return }
let player = AVPlayer(url: url)
self.player = player
player.play()
player.play() // 비디오 재생
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,45 +16,50 @@ class ExerciseTimerViewModel: ObservableObject {
private var timer: Timer?

init(initialSeconds: Int = 10) {
self.timerSeconds = initialSeconds
self.timerSeconds = initialSeconds // 초기 타이머 설정
}

/// 시간을 감소시킵니다.
func decreaseTime() {
if timerSeconds > 5 {
timerSeconds -= 5
timerSeconds -= 5 // 최소 5초로 유지
}
}

/// 시간을 증가시킵니다.
func increaseTime() {
timerSeconds += 5
timerSeconds += 5 // 5초 증가
}

/// 시간을 문자열 형태로 변환합니다.
func timeString() -> String {
let minutes = timerSeconds / 60
let remainingSeconds = timerSeconds % 60
return String(format: "%02d:%02d", minutes, remainingSeconds)
return String(format: "%02d:%02d", minutes, remainingSeconds) // "MM:SS" 형식
}

/// 타이머를 시작합니다.
func startTimer(completion: @escaping () -> Void) {
isTimerRunning = true
isTimerRunning = true // 타이머 실행 중 설정
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in
guard let self = self else { return }
if self.timerSeconds > 0 {
self.timerSeconds -= 1
self.timerSeconds -= 1 // 1초마다 감소
} else {
self.stopTimer()
completion()
self.stopTimer() // 타이머 중지
completion() // 완료 핸들러 호출
}
}
}

/// 타이머를 중지합니다.
func stopTimer() {
timer?.invalidate()
timer?.invalidate() // 타이머 무효화
timer = nil
isTimerRunning = false
isTimerRunning = false // 타이머 실행 중 설정 해제
}

deinit {
stopTimer()
stopTimer() // 메모리 해제 시 타이머 중지
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ class ChatViewModel: ObservableObject {
["role": "user", "content": content]
]
]

let request = AF.request(url, method: .post, parameters: body, encoding: JSONEncoding.default, headers: headers)

let response = await request.serializingDecodable(ChatResponse.self).response

switch response.result {
case .success(let chatResponse):
if let message = chatResponse.choices.first?.message {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ class AzureInbodyViewModel: ObservableObject {
DispatchQueue.main.async {
self?.isLoading = false
self?.error = .dataParsingError
print("data parsing error")
}
}
case .failure(let error):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@ class MemberViewModel: ObservableObject {
"Content-Type": "application/json",
"appleID": appleID
]

let request = AF.request(url, method: .post, headers: headers)

let response = await request.serializingDecodable(LoginResponse.self).response

switch response.result {
case .success(let loginResponse):
print("JWT 토큰: \(loginResponse.token)")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,20 @@ enum NetworkError: Error, LocalizedError {
}
}

enum HTTPMethod: String {
case get = "GET"
case post = "POST"
case patch = "PATCH"
case delete = "DELETE"
}
//enum HTTPMethod: String {
// case get = "GET"
// case post = "POST"
// case patch = "PATCH"
// case delete = "DELETE"
//}

class NetworkManager {
static func fetch<T: Codable>(_ endpoint: String, method: HTTPMethod = .get, body: T? = nil, multipartData: [String: Data]? = nil) async throws -> T {
static func fetch<T: Codable>(
_ endpoint: String,
method: HTTPMethod = .get,
body: T? = nil,
multipartData: [String: Data]? = nil
) async throws -> T {
let requestURL = URL(string: Config.baseURL + endpoint)!

return try await withCheckedThrowingContinuation { continuation in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ struct EyeBodyView: View {
@EnvironmentObject var onboardingNavigationPathFinder: NavigationPathFinder<OnboardingViewOptions>
@EnvironmentObject var profileNavigationPathFinder: NavigationPathFinder<ProfileViewOptions>
@StateObject private var viewModel = EyeBodyPhotoViewModel()
@EnvironmentObject var tabViewModel: TabViewModel
@Environment(\.modelContext) private var modelContext

var isOnboarding: Bool = true
Expand Down Expand Up @@ -107,6 +108,12 @@ struct EyeBodyView: View {
.padding(.top, 30)
}
.navigationBarBackButtonHidden()
.onAppear {
tabViewModel.isHidden = true
}
.onDisappear {
tabViewModel.isHidden = false
}
}

func getPlaceholder(for index: Int) -> String {
Expand Down
Loading