Skip to content

Commit

Permalink
Merge pull request #88 from aengzu/feature/record
Browse files Browse the repository at this point in the history
[Feat] 녹음 기능 구현
  • Loading branch information
aengzu authored Oct 27, 2024
2 parents 04498c7 + 11fa159 commit 2246da1
Show file tree
Hide file tree
Showing 29 changed files with 877 additions and 187 deletions.
15 changes: 14 additions & 1 deletion .idea/deploymentTargetDropDown.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions core/data/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ dependencies {
implementation(libs.retrofit.core)
implementation(libs.retrofit.kotlin.serialization)
implementation(libs.retrofit.gson)
implementation(libs.room.ktx)
kapt(libs.room.compiler)
implementation(libs.room.runtime)
implementation(libs.room.ktx)
implementation(libs.coroutines.android)
implementation(projects.core.domain) // domain 에 있는 걸 구현하므로
kapt(libs.room.compiler)
implementation(projects.core.domain)
implementation(libs.okhttp.logging)
}

Expand Down
8 changes: 8 additions & 0 deletions core/data/src/main/java/com/iguana/data/di/DataModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import java.io.File
import javax.inject.Singleton

@Module
Expand Down Expand Up @@ -48,5 +49,12 @@ abstract class DataModule {
fun provideRecentFileDao(appDatabase: AppDatabase): RecentFileDao {
return appDatabase.recentFileDao()
}

@Provides
@Singleton
fun provideBaseDir(@ApplicationContext context: Context): File {
// 앱의 내부 파일 디렉토리를 기본 디렉토리로 사용
return context.filesDir
}
}
}
7 changes: 7 additions & 0 deletions core/data/src/main/java/com/iguana/data/di/NetworkModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.iguana.data.di
import com.iguana.data.BuildConfig
import com.iguana.data.remote.api.AnnotationApi
import com.iguana.data.remote.api.DocumentApi
import com.iguana.data.remote.api.RecordApi
import com.iguana.data.remote.api.SummarizeApi
import com.iguana.domain.repository.SharedPreferencesHelper
import dagger.Module
Expand Down Expand Up @@ -59,4 +60,10 @@ object NetworkModule {
fun provideSummarizeApi(retrofit: Retrofit): SummarizeApi {
return retrofit.create(SummarizeApi::class.java)
}

@Provides
@Singleton
fun provideRecordApi(retrofit: Retrofit): RecordApi {
return retrofit.create(RecordApi::class.java)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import com.iguana.data.repository.AnnotationRepositoryImpl
import com.iguana.data.repository.DocumentsRepositoryImpl
import com.iguana.data.repository.LoginRepositoryImpl
import com.iguana.data.repository.RecentFileRepositoryImpl
import com.iguana.data.repository.RecordRepositoryImpl
import com.iguana.domain.repository.AIRepository
import com.iguana.domain.repository.AnnotationRepository
import com.iguana.domain.repository.DocumentsRepository
import com.iguana.domain.repository.LoginRepository
import com.iguana.domain.repository.RecentFileRepository
import com.iguana.domain.repository.RecordRepository
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
Expand Down Expand Up @@ -48,4 +50,11 @@ abstract class RepositoryModule {
abstract fun bindAIRepository(
aiRepositoryImpl: AIRepositoryImpl
): AIRepository

@Binds
@Singleton
abstract fun bindRecordingRepository(
recordRepositoryImpl: RecordRepositoryImpl
): RecordRepository

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import java.io.File
import javax.inject.Inject

class RecordingFileStorage @Inject constructor(
private val baseDir: File, // 내부 저장소 경로 (context.filesDir 등을 사용할 수 있음)
private val baseDir: File
) {

// 로컬 스토리지에 녹음 파일 저장
Expand All @@ -16,16 +16,17 @@ class RecordingFileStorage @Inject constructor(
}

// 페이지 이동 이벤트 저장
fun savePageTurnEvents(recordingId: Long, events: List<PageTurnEventDto>) {
val file = File(baseDir, "page_turn_events_$recordingId.txt")
file.writeText(events.joinToString(separator = "\n") { event ->
fun savePageTurnEvents(documentId: Long, events: List<PageTurnEventDto>) {
val file = File(baseDir, "page_turn_events_$documentId.txt")
// 기존 파일 내용에 이어서 새 이벤트를 추가
file.appendText(events.joinToString(separator = "\n") { event ->
"${event.prevPage},${event.nextPage},${event.timestamp}"
})
} + "\n")
}

// 로컬 스토리지에서 페이지 이동 이벤트 삭제
fun deletePageTurnEvents(recordingId: Long) {
val file = File(baseDir, "page_turn_events_$recordingId.txt")
fun deletePageTurnEvents(documentId: Long) {
val file = File(baseDir, "page_turn_events_$documentId.txt")
if (file.exists()) {
file.delete()
Result.success(Unit)
Expand All @@ -39,4 +40,20 @@ class RecordingFileStorage @Inject constructor(
val file = File(filePath)
return file.exists()
}

// 페이지 이동 이벤트 로드
fun loadPageTurnEvents(documentId: Long): List<PageTurnEventDto> {
val file = File(baseDir, "page_turn_events_$documentId.txt")
if (!file.exists()) throw AppError.FileNotFound

return file.readLines().map { line ->
val (prevPage, nextPage, timestamp) = line.split(",")
PageTurnEventDto(
prevPage = prevPage.toInt(),
nextPage = nextPage.toInt(),
timestamp = timestamp.toDouble()
)
}
}

}
31 changes: 23 additions & 8 deletions core/data/src/main/java/com/iguana/data/mapper/RecordMapper.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.iguana.data.mapper

import android.os.Build
import androidx.annotation.RequiresApi
import com.iguana.data.remote.model.PageTurnEventDto
import com.iguana.data.remote.model.PageTurnEventRequestDto
import com.iguana.data.remote.model.RecordingUploadRequestDto
Expand All @@ -15,24 +13,41 @@ import java.util.Base64
fun List<PageTurnEvent>.toPageTurnEventRequestDto(recordingId: Long): PageTurnEventRequestDto {
return PageTurnEventRequestDto(
recordingId = recordingId,
events = this.toPageTurnEventDtoList()
events = this.map { event ->
PageTurnEventDto(
prevPage = event.prevPage,
nextPage = event.nextPage,
timestamp = event.timestamp
)
}
)
}

// PageTurnEvent(DTO List) -> PageTurnEvent(Domain List)

fun List<PageTurnEventDto>.toPageTurnEventDomainList(documentId: Long): List<PageTurnEvent> {
return this.map { dto ->
PageTurnEvent(
documentId = documentId,
prevPage = dto.prevPage,
nextPage = dto.nextPage,
timestamp = dto.timestamp
)
}
}

// PageTurnEvents(Domain List) -> PageTurnEvents(DTO List)
fun List<PageTurnEvent>.toPageTurnEventDtoList(): List<PageTurnEventDto> {
return this.map { event ->
PageTurnEventDto(
prevPage = event.pageNumber - 1,
nextPage = event.pageNumber,
prevPage = event.prevPage,
nextPage = event.nextPage,
timestamp = event.timestamp.toDouble()
)
}
}


// RecordingFile을 RecordingUploadRequestDto로 변환하는 함수 (녹음 파일 업로드)

@RequiresApi(Build.VERSION_CODES.O)
fun RecordingFile.toUploadRequestDto(): RecordingUploadRequestDto {
// 파일을 Base64로 인코딩
val fileContent = File(this.filePath).readBytes()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ data class RecordingUploadRequestDto(
data class RecordingUploadResponseDto(
val recordingId: Long,
val documentId: Long,
val url: String,
val createdAt: String // ISO 8601 format
)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.iguana.data.repository

import android.util.Log
import com.iguana.data.local.files.RecordingFileStorage
import com.iguana.data.mapper.toPageTurnEventDomainList
import com.iguana.data.mapper.toPageTurnEventDtoList
import com.iguana.data.mapper.toPageTurnEventRequestDto
import com.iguana.data.mapper.toUploadRequestDto
Expand All @@ -24,12 +26,20 @@ class RecordRepositoryImpl @Inject constructor(
override suspend fun uploadRecordingFile(recordingFile: RecordingFile): RecordingFile {
return withContext(Dispatchers.IO) {
val uploadRequest = recordingFile.toUploadRequestDto()
val response = recordApi.uploadRecording(recordingFile.documentName.toLong(), uploadRequest)
Log.d("RecordRepositoryImpl", "Upload request 생성 완료: $uploadRequest")
val response = recordApi.uploadRecording(
recordingFile.documentId ?: throw AppError.NullResponseError("Document ID가 없습니다."),
uploadRequest
)
Log.d("RecordRepositoryImpl", "API 응답 상태: ${response.isSuccessful}")

if (response.isSuccessful) {
val body = response.body() ?: throw AppError.NullResponseError("녹음 파일 업로드 응답이 비어 있습니다.")
Log.d("RecordRepositoryImpl", "API 응답 상태: ${response.isSuccessful}")
val body =
response.body() ?: throw AppError.NullResponseError("녹음 파일 업로드 응답이 비어 있습니다.")
return@withContext recordingFile.updateWithResponse(body)
} else {
Log.e("RecordRepositoryImpl", "API 요청 실패: 코드 ${response.code()}, 메시지 ${response.message()}")
throw AppError.UploadFailed
}
}
Expand All @@ -47,10 +57,10 @@ class RecordRepositoryImpl @Inject constructor(
}

// 로컬에 있는 녹음 파일 삭제
override suspend fun deleteRecordingFile(recordingFile: RecordingFile) {
override suspend fun deleteRecordingFile(filePath: String) {
return withContext(Dispatchers.IO) {
if (localStorage.isFileExists(recordingFile.filePath)) {
val file = File(recordingFile.filePath)
if (localStorage.isFileExists(filePath)) {
val file = File(filePath)
file.delete()
} else {
throw AppError.FileNotFound
Expand All @@ -71,11 +81,11 @@ class RecordRepositoryImpl @Inject constructor(
}

// 로컬 스토리지에 페이지 이동 이벤트 저장
override suspend fun savePageTurnEvents(recordingId: Long, events: List<PageTurnEvent>) {
override suspend fun savePageTurnEvents(recordingId: Long, event: PageTurnEvent) {
withContext(Dispatchers.IO) {
localStorage.savePageTurnEvents(
recordingId,
events.toPageTurnEventDtoList()
listOf(event).toPageTurnEventDtoList()
) // 페이지 이동 이벤트 로컬에 저장
}
}
Expand All @@ -86,5 +96,13 @@ class RecordRepositoryImpl @Inject constructor(
localStorage.deletePageTurnEvents(recordingId) // 페이지 이동 이벤트 삭제
}
}

// 로컬에서 모든 페이지 이동 이벤트를 로드
override suspend fun loadPageTurnEvents(documentId: Long): List<PageTurnEvent> {
return withContext(Dispatchers.IO) {
localStorage.loadPageTurnEvents(documentId)
.toPageTurnEventDomainList(documentId) // 로컬에 저장된 페이지 이동 이벤트 로드
}
}
}

Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package com.iguana.domain.model.record

data class PageTurnEvent (
data class PageTurnEvent(
val documentId: Long,
val pageNumber: Int,
val timestamp: Long
val prevPage : Int,
val nextPage : Int,
val timestamp: Double
)
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,18 @@ interface RecordRepository {
suspend fun saveRecordingFile(recordingFile: RecordingFile)

// 로컬에 있는 녹음 파일 삭제
suspend fun deleteRecordingFile(recordingFile: RecordingFile)
suspend fun deleteRecordingFile(filePath: String)

// 페이지 이동 이벤트 업로드
suspend fun uploadPageTurnEvents(recordingId: Long, events: List<PageTurnEvent>)

// 로컬 스토리지에 페이지 이동 이벤트 저장
suspend fun savePageTurnEvents(recordingId: Long, events: List<PageTurnEvent>)
suspend fun savePageTurnEvents(recordingId: Long, event: PageTurnEvent)

// 로컬에 저장된 페이지 이동 이벤트 삭제
suspend fun deletePageTurnEvents(recordingId: Long)

// 로컬에서 모든 페이지 이동 이벤트를 로드
suspend fun loadPageTurnEvents(documentId: Long): List<PageTurnEvent>

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.iguana.domain.usecase

import com.iguana.domain.repository.RecordRepository
import javax.inject.Inject

class DeletePageTurnEventsUseCase @Inject constructor(
private val recordRepository: RecordRepository
) {
suspend operator fun invoke(documentId: Long) {
recordRepository.deletePageTurnEvents(documentId)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.iguana.domain.usecase

import com.iguana.domain.repository.RecordRepository
import javax.inject.Inject

class DeleteRecordingUseCase @Inject constructor(
private val recordRepository: RecordRepository
) {
suspend operator fun invoke(filePath: String) {
recordRepository.deleteRecordingFile(filePath)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.iguana.domain.usecase
import com.iguana.domain.model.record.PageTurnEvent
import com.iguana.domain.repository.RecordRepository
import javax.inject.Inject

class SavePageTurnEventUseCase @Inject constructor(
private val recordRepository: RecordRepository
) {
suspend operator fun invoke(documentId: Long, prevPage: Int?, currentPage: Int, startTimeMillis: Long) {
// 현재 시간과 녹음 시작 시간을 바탕으로 초 단위 타임스탬프 계산
val timestamp = (System.currentTimeMillis() - startTimeMillis) / 1000.0

// PageTurnEvent 생성
val event = PageTurnEvent(
documentId = documentId,
prevPage = prevPage ?: currentPage,
nextPage = currentPage,
timestamp = timestamp
)

// Repository를 통해 이벤트 저장
recordRepository.savePageTurnEvents(documentId, event)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.iguana.domain.usecase

import com.iguana.domain.model.record.PageTurnEvent
import com.iguana.domain.repository.RecordRepository
import javax.inject.Inject

class UploadPageTurnEventsUseCase @Inject constructor(
private val recordRepository: RecordRepository
) {
suspend operator fun invoke(documentId: Long, recordingId: Long) {
val events = recordRepository.loadPageTurnEvents(documentId)
recordRepository.uploadPageTurnEvents(recordingId, events)
}
}
Loading

0 comments on commit 2246da1

Please sign in to comment.