diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml
index 0c0c338..03942f2 100644
--- a/.idea/deploymentTargetDropDown.xml
+++ b/.idea/deploymentTargetDropDown.xml
@@ -3,7 +3,20 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core/data/build.gradle.kts b/core/data/build.gradle.kts
index 6e352f3..73a9b08 100644
--- a/core/data/build.gradle.kts
+++ b/core/data/build.gradle.kts
@@ -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)
}
diff --git a/core/data/src/main/java/com/iguana/data/di/DataModule.kt b/core/data/src/main/java/com/iguana/data/di/DataModule.kt
index b4477cc..c709338 100644
--- a/core/data/src/main/java/com/iguana/data/di/DataModule.kt
+++ b/core/data/src/main/java/com/iguana/data/di/DataModule.kt
@@ -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
@@ -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
+ }
}
}
\ No newline at end of file
diff --git a/core/data/src/main/java/com/iguana/data/di/NetworkModule.kt b/core/data/src/main/java/com/iguana/data/di/NetworkModule.kt
index 988bc06..5aae0b5 100644
--- a/core/data/src/main/java/com/iguana/data/di/NetworkModule.kt
+++ b/core/data/src/main/java/com/iguana/data/di/NetworkModule.kt
@@ -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
@@ -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)
+ }
}
diff --git a/core/data/src/main/java/com/iguana/data/di/RepositoryModule.kt b/core/data/src/main/java/com/iguana/data/di/RepositoryModule.kt
index 05c532e..20444ad 100644
--- a/core/data/src/main/java/com/iguana/data/di/RepositoryModule.kt
+++ b/core/data/src/main/java/com/iguana/data/di/RepositoryModule.kt
@@ -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
@@ -48,4 +50,11 @@ abstract class RepositoryModule {
abstract fun bindAIRepository(
aiRepositoryImpl: AIRepositoryImpl
): AIRepository
+
+ @Binds
+ @Singleton
+ abstract fun bindRecordingRepository(
+ recordRepositoryImpl: RecordRepositoryImpl
+ ): RecordRepository
+
}
\ No newline at end of file
diff --git a/core/data/src/main/java/com/iguana/data/local/files/RecordingFileStorage.kt b/core/data/src/main/java/com/iguana/data/local/files/RecordingFileStorage.kt
index ac88ecf..7ec4d23 100644
--- a/core/data/src/main/java/com/iguana/data/local/files/RecordingFileStorage.kt
+++ b/core/data/src/main/java/com/iguana/data/local/files/RecordingFileStorage.kt
@@ -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
) {
// 로컬 스토리지에 녹음 파일 저장
@@ -16,16 +16,17 @@ class RecordingFileStorage @Inject constructor(
}
// 페이지 이동 이벤트 저장
- fun savePageTurnEvents(recordingId: Long, events: List) {
- val file = File(baseDir, "page_turn_events_$recordingId.txt")
- file.writeText(events.joinToString(separator = "\n") { event ->
+ fun savePageTurnEvents(documentId: Long, events: List) {
+ 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)
@@ -39,4 +40,20 @@ class RecordingFileStorage @Inject constructor(
val file = File(filePath)
return file.exists()
}
+
+ // 페이지 이동 이벤트 로드
+ fun loadPageTurnEvents(documentId: Long): List {
+ 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()
+ )
+ }
+ }
+
}
diff --git a/core/data/src/main/java/com/iguana/data/mapper/RecordMapper.kt b/core/data/src/main/java/com/iguana/data/mapper/RecordMapper.kt
index c0a8a9c..31aa207 100644
--- a/core/data/src/main/java/com/iguana/data/mapper/RecordMapper.kt
+++ b/core/data/src/main/java/com/iguana/data/mapper/RecordMapper.kt
@@ -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
@@ -15,24 +13,41 @@ import java.util.Base64
fun List.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.toPageTurnEventDomainList(documentId: Long): List {
+ return this.map { dto ->
+ PageTurnEvent(
+ documentId = documentId,
+ prevPage = dto.prevPage,
+ nextPage = dto.nextPage,
+ timestamp = dto.timestamp
+ )
+ }
+}
+
+// PageTurnEvents(Domain List) -> PageTurnEvents(DTO List)
fun List.toPageTurnEventDtoList(): List {
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()
diff --git a/core/data/src/main/java/com/iguana/data/remote/model/RecordDto.kt b/core/data/src/main/java/com/iguana/data/remote/model/RecordDto.kt
index 3d11e9f..332ddfa 100644
--- a/core/data/src/main/java/com/iguana/data/remote/model/RecordDto.kt
+++ b/core/data/src/main/java/com/iguana/data/remote/model/RecordDto.kt
@@ -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
)
diff --git a/core/data/src/main/java/com/iguana/data/repository/RecordRepositoryImpl.kt b/core/data/src/main/java/com/iguana/data/repository/RecordRepositoryImpl.kt
index 08c8ad5..060525b 100644
--- a/core/data/src/main/java/com/iguana/data/repository/RecordRepositoryImpl.kt
+++ b/core/data/src/main/java/com/iguana/data/repository/RecordRepositoryImpl.kt
@@ -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
@@ -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
}
}
@@ -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
@@ -71,11 +81,11 @@ class RecordRepositoryImpl @Inject constructor(
}
// 로컬 스토리지에 페이지 이동 이벤트 저장
- override suspend fun savePageTurnEvents(recordingId: Long, events: List) {
+ override suspend fun savePageTurnEvents(recordingId: Long, event: PageTurnEvent) {
withContext(Dispatchers.IO) {
localStorage.savePageTurnEvents(
recordingId,
- events.toPageTurnEventDtoList()
+ listOf(event).toPageTurnEventDtoList()
) // 페이지 이동 이벤트 로컬에 저장
}
}
@@ -86,5 +96,13 @@ class RecordRepositoryImpl @Inject constructor(
localStorage.deletePageTurnEvents(recordingId) // 페이지 이동 이벤트 삭제
}
}
+
+ // 로컬에서 모든 페이지 이동 이벤트를 로드
+ override suspend fun loadPageTurnEvents(documentId: Long): List {
+ return withContext(Dispatchers.IO) {
+ localStorage.loadPageTurnEvents(documentId)
+ .toPageTurnEventDomainList(documentId) // 로컬에 저장된 페이지 이동 이벤트 로드
+ }
+ }
}
diff --git a/core/domain/src/main/java/com/iguana/domain/model/record/PageTurnEvent.kt b/core/domain/src/main/java/com/iguana/domain/model/record/PageTurnEvent.kt
index 9b987c5..25bac43 100644
--- a/core/domain/src/main/java/com/iguana/domain/model/record/PageTurnEvent.kt
+++ b/core/domain/src/main/java/com/iguana/domain/model/record/PageTurnEvent.kt
@@ -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
)
\ No newline at end of file
diff --git a/core/domain/src/main/java/com/iguana/domain/repository/RecordRepository.kt b/core/domain/src/main/java/com/iguana/domain/repository/RecordRepository.kt
index f8e6c63..45d4a7b 100644
--- a/core/domain/src/main/java/com/iguana/domain/repository/RecordRepository.kt
+++ b/core/domain/src/main/java/com/iguana/domain/repository/RecordRepository.kt
@@ -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)
// 로컬 스토리지에 페이지 이동 이벤트 저장
- suspend fun savePageTurnEvents(recordingId: Long, events: List)
+ suspend fun savePageTurnEvents(recordingId: Long, event: PageTurnEvent)
// 로컬에 저장된 페이지 이동 이벤트 삭제
suspend fun deletePageTurnEvents(recordingId: Long)
+ // 로컬에서 모든 페이지 이동 이벤트를 로드
+ suspend fun loadPageTurnEvents(documentId: Long): List
+
}
diff --git a/core/domain/src/main/java/com/iguana/domain/usecase/DeletePageTurnEventsUseCase.kt b/core/domain/src/main/java/com/iguana/domain/usecase/DeletePageTurnEventsUseCase.kt
new file mode 100644
index 0000000..63576c5
--- /dev/null
+++ b/core/domain/src/main/java/com/iguana/domain/usecase/DeletePageTurnEventsUseCase.kt
@@ -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)
+ }
+}
\ No newline at end of file
diff --git a/core/domain/src/main/java/com/iguana/domain/usecase/DeleteRecordingUseCase.kt b/core/domain/src/main/java/com/iguana/domain/usecase/DeleteRecordingUseCase.kt
new file mode 100644
index 0000000..61a8cc6
--- /dev/null
+++ b/core/domain/src/main/java/com/iguana/domain/usecase/DeleteRecordingUseCase.kt
@@ -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)
+ }
+}
\ No newline at end of file
diff --git a/core/domain/src/main/java/com/iguana/domain/usecase/SavePageTurnEventUseCase.kt b/core/domain/src/main/java/com/iguana/domain/usecase/SavePageTurnEventUseCase.kt
new file mode 100644
index 0000000..15dcc57
--- /dev/null
+++ b/core/domain/src/main/java/com/iguana/domain/usecase/SavePageTurnEventUseCase.kt
@@ -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)
+ }
+}
diff --git a/core/domain/src/main/java/com/iguana/domain/usecase/UploadPageTurnEventsUseCase.kt b/core/domain/src/main/java/com/iguana/domain/usecase/UploadPageTurnEventsUseCase.kt
new file mode 100644
index 0000000..24f5bce
--- /dev/null
+++ b/core/domain/src/main/java/com/iguana/domain/usecase/UploadPageTurnEventsUseCase.kt
@@ -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)
+ }
+}
\ No newline at end of file
diff --git a/core/domain/src/main/java/com/iguana/domain/usecase/UploadRecordingUseCase.kt b/core/domain/src/main/java/com/iguana/domain/usecase/UploadRecordingUseCase.kt
new file mode 100644
index 0000000..8620c35
--- /dev/null
+++ b/core/domain/src/main/java/com/iguana/domain/usecase/UploadRecordingUseCase.kt
@@ -0,0 +1,40 @@
+package com.iguana.domain.usecase
+
+import android.util.Log
+import com.iguana.domain.model.record.RecordingFile
+import com.iguana.domain.repository.RecordRepository
+import java.io.File
+import javax.inject.Inject
+
+class UploadRecordingUseCase @Inject constructor(
+ private val recordRepository: RecordRepository
+) {
+ suspend operator fun invoke(
+ documentId: Long,
+ filePath: String,
+ fileName: String
+ ): Long {
+ val recordingFile = createRecordingFile(documentId, filePath, fileName)
+ val uploadedRecording = recordRepository.uploadRecordingFile(recordingFile)
+
+ return uploadedRecording.recordingId ?: throw Exception("recordingId가 없습니다")
+ }
+
+ // RecordingFile 객체 생성 메서드
+ private fun createRecordingFile(
+ documentId: Long,
+ filePath: String,
+ fileName: String
+ ): RecordingFile {
+ val file = File(filePath)
+ return RecordingFile(
+ filePath = filePath,
+ fileSize = file.length(),
+ format = "3gp",
+ duration = 0L, // 녹음 길이 설정 필요 시 추가
+ documentName = fileName,
+ recordingId = null,
+ documentId = documentId
+ )
+ }
+}
\ No newline at end of file
diff --git a/feature/dashBoard/src/main/java/com/iguana/dashBoard/RecentFilesViewModel.kt b/feature/dashBoard/src/main/java/com/iguana/dashBoard/RecentFilesViewModel.kt
index 5b7ec2b..b4193f6 100644
--- a/feature/dashBoard/src/main/java/com/iguana/dashBoard/RecentFilesViewModel.kt
+++ b/feature/dashBoard/src/main/java/com/iguana/dashBoard/RecentFilesViewModel.kt
@@ -57,7 +57,7 @@ class RecentFilesViewModel @Inject constructor(
val intent = Intent(context, NotetakingActivity::class.java).apply {
putExtra("PDF_URI", recentFile.fileUri)
putExtra("PDF_TITLE", recentFile.fileName)
- putExtra("DOCUMENT_ID", recentFile.id.toString())
+ putExtra("DOCUMENT_ID", recentFile.id)
}
context.startActivity(intent)
}
@@ -117,10 +117,10 @@ class RecentFilesViewModel @Inject constructor(
// TODO: 서버완료되면 아래 코드 삭제 후 위 코드 주석해제
val intent = Intent(context, NotetakingActivity::class.java).apply {
- putExtra("PDF_URI", internalUri)
+ putExtra("PDF_URI", internalUri.toString())
putExtra("PDF_TITLE", fileName)
// 시간으로 더미값 생성해서 넣기
- putExtra("DOCUMENT_ID", System.currentTimeMillis().toString())
+ putExtra("DOCUMENT_ID", System.currentTimeMillis())
}
context.startActivity(intent)
} catch (e: Exception) {
diff --git a/feature/notetaking/src/main/AndroidManifest.xml b/feature/notetaking/src/main/AndroidManifest.xml
index 730dd70..a575235 100644
--- a/feature/notetaking/src/main/AndroidManifest.xml
+++ b/feature/notetaking/src/main/AndroidManifest.xml
@@ -5,15 +5,14 @@
-
-
-
+
+
+
\ No newline at end of file
diff --git a/feature/notetaking/src/main/java/com/iguana/notetaking/NotetakingActivity.kt b/feature/notetaking/src/main/java/com/iguana/notetaking/NotetakingActivity.kt
index 139c7ef..d96748a 100644
--- a/feature/notetaking/src/main/java/com/iguana/notetaking/NotetakingActivity.kt
+++ b/feature/notetaking/src/main/java/com/iguana/notetaking/NotetakingActivity.kt
@@ -1,18 +1,33 @@
package com.iguana.notetaking
+import android.Manifest
+import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
import android.util.Log
+import android.view.WindowInsets.Side
+import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import com.iguana.notetaking.databinding.ActivityNotetakingBinding
+import com.iguana.notetaking.recording.RecordFragment
import com.iguana.notetaking.sidebar.SideBarFragment
import dagger.hilt.android.AndroidEntryPoint
+
@AndroidEntryPoint
class NotetakingActivity : AppCompatActivity() {
+
+ companion object {
+ const val PDF_URI_KEY = "PDF_URI"
+ const val PDF_TITLE_KEY = "PDF_TITLE"
+ const val DEFAULT_TITLE = "무제"
+ const val DOCUMENT_ID_KEY = "DOCUMENT_ID"
+ }
+
+
private lateinit var binding: ActivityNotetakingBinding
private val viewModel: NotetakingViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
@@ -24,44 +39,141 @@ class NotetakingActivity : AppCompatActivity() {
binding.viewModel = viewModel
binding.lifecycleOwner = this
- // Intent에서 PDF URI 받기
- val pdfUriString = intent.getStringExtra("PDF_URI")
- val pdfTitle = intent.getStringExtra("PDF_TITLE") ?: "무제"
- viewModel.documentId = intent.getLongExtra("DOCUMENT_ID", -1)
+ initializeView()
+ observeToolbar()
+ }
+
+ // 뷰 초기화 메서드
+ private fun initializeView() {
+ viewModel.pdfUri = intent.getStringExtra(PDF_URI_KEY).toString()
+ viewModel.pdfTitle = intent.getStringExtra(PDF_TITLE_KEY) ?: DEFAULT_TITLE
+ viewModel.documentId = intent.getLongExtra(DOCUMENT_ID_KEY, -1)
+
+ setupTitleBar()
+ setupToolbar()
+ setupPdfViewerAndSidebar()
+ }
+
+ // 페이지 변경 시 호출되는 메서드
+ fun onPageChanged(pageNumber: Int) {
+ viewModel.setPageNumber(pageNumber)
+ updateSidebarWithPage(pageNumber)
+ }
+
+ // 툴바 설정
+ private fun setupToolbar() {
+ binding.toolbar.btnText.setOnClickListener {
+ val pdfViewerFragment = getPdfViewerFragment()
+ pdfViewerFragment?.getCurrentPdfPageFragment()?.addTextBox()
+ }
+ binding.toolbar.btnRecord.setOnClickListener {
+ requestAudioPermissions()
+ viewModel.toggleRecordTabActive()
+ }
+ binding.toolbar.btnAI.setOnClickListener {
+ viewModel.toggleAITabActive()
+ }
+ }
- binding.titleBar.titleBar.text = pdfTitle
- // 뒤로가기 버튼 클릭 리스너 설정
+ // 타이틀바 설정
+ private fun setupTitleBar() {
binding.titleBar.backButton.setOnClickListener {
finish()
}
+ binding.titleBar.titleBar.text = viewModel.pdfTitle
+ }
- // PDF URI가 있는 경우 프래그먼트 추가
- if (pdfUriString != null) {
- val pdfUri = Uri.parse(pdfUriString)
- val pdfViewerFragment = PdfViewerFragment.newInstance(pdfUri)
+ // PDF 및 사이드바 초기화 메서드
+ private fun setupPdfViewerAndSidebar() {
+ val pdfUri = Uri.parse(viewModel.pdfUri)
+ replaceFragment(R.id.pdf_fragment_container, PdfViewerFragment.newInstance(pdfUri))
+ replaceFragment(
+ R.id.side_bar_container,
+ SideBarFragment.newInstance(viewModel.documentId, viewModel.pageNumber.value ?: 0)
+ )
+ }
- supportFragmentManager.beginTransaction()
- .replace(R.id.pdf_fragment_container, pdfViewerFragment)
- .commit()
+ // 프래그먼트 교체 메서드
+ private fun replaceFragment(containerId: Int, fragment: androidx.fragment.app.Fragment) {
+ supportFragmentManager.beginTransaction()
+ .replace(containerId, fragment)
+ .commit()
+ }
- val sideBarFragment = SideBarFragment.newInstance(viewModel.documentId, viewModel.pageNumber.value ?: 0)
- supportFragmentManager.beginTransaction()
- .replace(R.id.side_bar_container, sideBarFragment)
- .commit()
+ // PDF 에러 처리 메서드
+ private fun handlePdfError(message: String) {
+ Log.e("NotetakingActivity", message)
+ Toast.makeText(this, "PDF 파일을 열 수 없습니다.", Toast.LENGTH_SHORT).show()
+ }
- // 툴바의 텍스트 버튼 클릭 리스너 설정
- binding.toolbar.btnText.setOnClickListener {
- pdfViewerFragment.getCurrentPdfPageFragment()?.addTextBox()
+ // PDF 뷰어 프래그먼트 가져오기 메서드
+ private fun getPdfViewerFragment(): PdfViewerFragment? {
+ return supportFragmentManager.findFragmentById(R.id.pdf_fragment_container) as? PdfViewerFragment
+ }
+
+ // 사이드바 프래그먼트 가져오기 메서드
+ private fun getSideBarFragment(): SideBarFragment? {
+ return supportFragmentManager.findFragmentById(R.id.side_bar_container) as? SideBarFragment
+ }
+
+ // 사이드바 업데이트 메서드
+ private fun updateSidebarWithPage(pageNumber: Int) {
+ val sideBarFragment = getSideBarFragment()
+ sideBarFragment?.updatePageNumber(pageNumber)
+ }
+
+ // 뷰모델의 상태를 관찰하여 UI 업데이트
+ private fun observeToolbar() {
+ // Record 탭의 활성화 상태 관찰
+ viewModel.isRecordActive.observe(this) { isActive ->
+ binding.toolbar.btnRecord.isSelected = isActive // 선택된 상태로 업데이트
+ if (isActive) {
+ startRecordingInFragment()
+ Toast.makeText(this, "녹음이 시작되었습니다.", Toast.LENGTH_SHORT).show()
+ } else if (viewModel.isRecordingStopped()) {
+ stopRecordingInFragment()
+ Toast.makeText(this, "녹음이 종료되었습니다.", Toast.LENGTH_SHORT).show()
}
+ }
+
+ // AI 탭의 활성화 상태 관찰
+ viewModel.isAIActive.observe(this) { isActive ->
+ binding.toolbar.btnAI.isSelected = isActive // 선택된 상태로 업데이트
+ }
+ }
+
+ private val RECORD_AUDIO_PERMISSION_REQUEST_CODE = 1001
+
+ // 권한 요청 메서드
+ private fun requestAudioPermissions() {
+ if (checkSelfPermission(Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
+ requestPermissions(arrayOf(Manifest.permission.RECORD_AUDIO), RECORD_AUDIO_PERMISSION_REQUEST_CODE)
} else {
- Log.e("NotetakingActivity", "PDF URI is null in Activity") // URI가 null인 경우 로그
+ // 이미 권한이 있는 경우
+ Log.d("NotetakingActivity", "이미 녹음 권한이 있습니다.")
}
}
- // 페이지 변경 시 호출되는 메서드
- fun onPageChanged(pageNumber: Int) {
- viewModel.setPageNumber(pageNumber)
- // 사이드바 프래그먼트 해당 페이지 내용으로 업데이트
- val sideBarFragment = supportFragmentManager.findFragmentById(R.id.side_bar_container) as? SideBarFragment
- sideBarFragment?.updatePageNumber(pageNumber)
+
+ // 권한 요청 결과 처리
+ override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+ if (requestCode == RECORD_AUDIO_PERMISSION_REQUEST_CODE) {
+ if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+
+ } else {
+ Toast.makeText(this, "녹음 권한이 필요합니다.", Toast.LENGTH_SHORT).show()
+ }
+ }
+ }
+ // 녹음 시작 메서드
+ private fun startRecordingInFragment() {
+ val sideBarFragment = getSideBarFragment() // SidebarFragment 가져오기
+ sideBarFragment?.startRecordingInRecordFragment() // SidebarFragment에 녹음 시작 요청
+ }
+
+ // 녹음 중지 메서드
+ private fun stopRecordingInFragment() {
+ val sideBarFragment = getSideBarFragment() // SidebarFragment 가져오기
+ sideBarFragment?.stopRecordingInRecordFragment() // SidebarFragment에 녹음 중지 요청
}
-}
\ No newline at end of file
+}
diff --git a/feature/notetaking/src/main/java/com/iguana/notetaking/NotetakingViewModel.kt b/feature/notetaking/src/main/java/com/iguana/notetaking/NotetakingViewModel.kt
index f7db99b..2b4a8e4 100644
--- a/feature/notetaking/src/main/java/com/iguana/notetaking/NotetakingViewModel.kt
+++ b/feature/notetaking/src/main/java/com/iguana/notetaking/NotetakingViewModel.kt
@@ -4,20 +4,40 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
+import com.iguana.notetaking.NotetakingActivity.Companion.DEFAULT_TITLE
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
@HiltViewModel
-class NotetakingViewModel @Inject constructor(
- handle: SavedStateHandle
-) : ViewModel() {
- private val _isSideBarVisible = MutableLiveData(true) // 초기값은 true로 설정
- val isSideBarVisible: LiveData get() = _isSideBarVisible
+class NotetakingViewModel @Inject constructor() : ViewModel() {
var documentId: Long = -1L
+ set(value) = run { field = value }
+ var pdfUri: String = ""
+ set(value) = run { field = value }
+
+ var pdfTitle: String? = "무제"
+ set(value) = run { field = value }
+
+
private val _pageNumber = MutableLiveData()
val pageNumber: LiveData get() = _pageNumber
+ private val _isSideBarVisible = MutableLiveData(true) // 초기값은 true로 설정
+ val isSideBarVisible: LiveData get() = _isSideBarVisible
+
+ // record 버튼 활성화되어있는지
+ private val _isRecordActive = MutableLiveData(false)
+ val isRecordActive: LiveData get() = _isRecordActive
+
+
+ // AI 버튼 활성화되어있는지
+ private val _isAIActive = MutableLiveData(false)
+ val isAIActive: LiveData get() = _isAIActive
+
+ // 이전 녹음 상태 추적 변수
+ private var wasRecording = false
+
fun setPageNumber(pageNumber: Int) {
_pageNumber.value = pageNumber
}
@@ -26,4 +46,31 @@ class NotetakingViewModel @Inject constructor(
fun toggleSideBarVisibility() {
_isSideBarVisible.value = _isSideBarVisible.value?.not()
}
+
+ // 사이드바 보이게 하는 함수
+ fun showSideBar() {
+ _isSideBarVisible.value = true
+ }
+ // 사이드바 숨기기
+ fun hideSideBar() {
+ _isSideBarVisible.value = false
+ }
+
+
+ // 액티브 상태를 토글
+ fun toggleRecordTabActive() {
+ _isRecordActive.value = _isRecordActive.value?.not()
+ // 상태가 변경될 때 이전 녹음 상태를 저장
+ wasRecording = _isRecordActive.value == true
+ showSideBar()
+ }
+ fun toggleAITabActive() {
+ _isAIActive.value = _isAIActive.value?.not()
+ showSideBar()
+ }
+
+ // 녹음이 종료되었는지 확인하는 함수 (이전에 녹음 중 -> 녹음 종료)
+ fun isRecordingStopped(): Boolean {
+ return wasRecording && !_isRecordActive.value!!
+ }
}
diff --git a/feature/notetaking/src/main/java/com/iguana/notetaking/recording/RecordFragment.kt b/feature/notetaking/src/main/java/com/iguana/notetaking/recording/RecordFragment.kt
index 6632d9d..51a6607 100644
--- a/feature/notetaking/src/main/java/com/iguana/notetaking/recording/RecordFragment.kt
+++ b/feature/notetaking/src/main/java/com/iguana/notetaking/recording/RecordFragment.kt
@@ -1,14 +1,18 @@
package com.iguana.notetaking.recording
+import android.content.Context
import android.os.Bundle
+import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.lifecycle.SavedStateViewModelFactory
import com.iguana.notetaking.NotetakingActivity
+import com.iguana.notetaking.NotetakingViewModel
import com.iguana.notetaking.ai.AiFragment
import com.iguana.notetaking.databinding.FragmentRecordBinding
import dagger.hilt.android.AndroidEntryPoint
@@ -50,10 +54,11 @@ class RecordFragment() : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
+ observeRecordingState()
}
- // 페이지 번호 업데이트 메서드
+ // 페이지 번호 업데이트 메서드 -> 페이지 이동 이벤트 발생시 상위 프래그먼트에서 호출되는 함수
fun updateContentForPage(pageNumber: Int) {
if (isAdded && !isDetached) { // Fragment가 활성 상태인지 확인
viewModel.setPageNumber(pageNumber+1)
@@ -65,4 +70,26 @@ class RecordFragment() : Fragment() {
_binding = null
}
+ fun startRecording(context: Context) {
+ Log.d("RecordFragment", "녹음이 시작되기 바로 직전입니다.")
+ viewModel.startRecording(context)
+ Log.d("RecordFragment", "녹음이 시작되었습니다.")
+ }
+
+ fun stopRecording(context: Context) {
+ viewModel.stopRecording(context)
+ }
+
+ private fun observeRecordingState() {
+ viewModel.recordingStatus.observe(viewLifecycleOwner) { isRecording ->
+ if (isRecording) {
+ binding.recordStatusTextView.text = "녹음 중"
+ } else {
+ binding.recordStatusTextView.text = "녹음 종료"
+ }
+ }
+ }
+
+
+
}
diff --git a/feature/notetaking/src/main/java/com/iguana/notetaking/recording/RecordViewModel.kt b/feature/notetaking/src/main/java/com/iguana/notetaking/recording/RecordViewModel.kt
index 92c577c..91738c5 100644
--- a/feature/notetaking/src/main/java/com/iguana/notetaking/recording/RecordViewModel.kt
+++ b/feature/notetaking/src/main/java/com/iguana/notetaking/recording/RecordViewModel.kt
@@ -1,24 +1,146 @@
package com.iguana.notetaking.recording
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.media.MediaRecorder
+import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
-import com.iguana.notetaking.ai.AiFragment
-import com.iguana.notetaking.sidebar.SideBarFragment
+import androidx.lifecycle.viewModelScope
+import com.iguana.domain.usecase.DeletePageTurnEventsUseCase
+import com.iguana.domain.usecase.DeleteRecordingUseCase
+import com.iguana.domain.usecase.SavePageTurnEventUseCase
+import com.iguana.domain.usecase.UploadPageTurnEventsUseCase
+import com.iguana.domain.usecase.UploadRecordingUseCase
import dagger.hilt.android.lifecycle.HiltViewModel
+import dagger.hilt.android.qualifiers.ApplicationContext
+import kotlinx.coroutines.launch
import javax.inject.Inject
+import javax.inject.Provider
+
@HiltViewModel
class RecordViewModel @Inject constructor(
- handle: SavedStateHandle
+ handle: SavedStateHandle,
+ private val uploadRecordingUseCase: UploadRecordingUseCase,
+ private val uploadPageTurnEventsUseCase: UploadPageTurnEventsUseCase,
+ private val savePageTurnEventUseCase: SavePageTurnEventUseCase,
+ private val deletePageTurnEventsUseCase: DeletePageTurnEventsUseCase,
+ private val deleteRecordingUseCase: DeleteRecordingUseCase,
+ @ApplicationContext private val contextProvider: Provider
) : ViewModel() {
var documentId: Long = -1L
+ private var recorder: MediaRecorder? = null
+
+
+ private val _recordingStatus = MutableLiveData()
+ val recordingStatus: LiveData get() = _recordingStatus
+
+ private var startTimeMillis: Long = 0L
- private val _pageNumber = MutableLiveData()
+ private var prevPage: Int = 1 // 이전 페이지 번호 추적
+ private val _pageNumber = MutableLiveData() // 현재 페이지 번호
val pageNumber: LiveData get() = _pageNumber
+ private var filePath: String? = null
+ private var fileName: String? = null
+
+
+ private val context by lazy { contextProvider.get() }
+
+ private val recordingReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent?) {
+ filePath = intent?.getStringExtra("filePath")
+ fileName = intent?.getStringExtra("fileName")
+ Log.d("RecordViewModel", "Recording finished: $filePath, $fileName")
+
+ processRecordingAndEvents()
+ }
+ }
+
+ init {
+ context.registerReceiver(
+ recordingReceiver, IntentFilter(BROADCAST_RECORDING_FINISHED)
+ )
+ }
fun setPageNumber(pageNumber: Int) {
_pageNumber.value = pageNumber
+ if (isRecording()) {
+ viewModelScope.launch {
+ savePageTurnEvent(pageNumber)
+ }
+ prevPage = pageNumber
+ } else {
+ Log.d("RecordViewModel", "녹음 상태가 아님, 저장하지 않음")
+ }
+ }
+
+ fun startRecording(context: Context) {
+ try {
+ val intent = Intent(context, RecordingService::class.java).apply {
+ action = ACTION_START_RECORDING
+ }
+ context.startService(intent)
+ _recordingStatus.value = true // 녹음 시작 전에 상태를 true로 설정
+ startTimeMillis = System.currentTimeMillis()
+ } catch (e: Exception) {
+ Log.e("RecordViewModel", "녹음 시작 실패: ${e.message}")
+ }
+ }
+
+ fun stopRecording(context: Context) {
+ sendStopIntent(context)
+ _recordingStatus.value = false
+ }
+
+ private fun sendStopIntent(context: Context) {
+ val intent = Intent(context, RecordingService::class.java).apply {
+ action = ACTION_STOP_RECORDING
+ }
+ context.startService(intent)
+ }
+
+ private fun processRecordingAndEvents() {
+ viewModelScope.launch {
+ try {
+ // filePath 또는 fileName이 null일 경우
+ if (filePath == null || fileName == null) {
+ Log.e("RecordViewModel", "filePath 또는 fileName이 null입니다. 업로드를 중단합니다.")
+ return@launch
+ }
+
+ // TODO: 녹음 파일 업로드 및 페이지 이동 이벤트 업로드 -> 로컬에 저장된 파일 삭제 하는 부분인데 서버측 완료되면 주석해제
+ // 1. 녹음 파일 업로드
+ // val recordingId = uploadRecordingUseCase(documentId, filePath ?: return@launch, fileName ?: return@launch)
+ // 2. 페이지 이동 이벤트 업로드
+ // uploadPageTurnEventsUseCase(documentId, recordingId)
+ // 3. 로컬에 저장된 페이지 이동 이벤트 파일 삭제
+// deletePageTurnEventsUseCase(documentId)
+ // 4. 로컬에 저장된 녹음 파일 삭제
+// deleteRecordingUseCase(filePath!!)
+ } catch (e: Exception) {
+ Log.e("RecordViewModel", "업로드 중 오류 발생: ${e.message}")
+ }
+ }
+ }
+
+ private suspend fun savePageTurnEvent(currentPage: Int) {
+ savePageTurnEventUseCase(documentId, prevPage, currentPage, startTimeMillis)
+ }
+
+ private fun isRecording(): Boolean {
+ return recordingStatus.value ?: false
}
+
+ companion object {
+ private const val ACTION_START_RECORDING = "START_RECORDING"
+ private const val ACTION_STOP_RECORDING = "STOP_RECORDING"
+ private const val BROADCAST_RECORDING_FINISHED = "com.iguana.notetaking.RECORDING_FINISHED"
+ }
+
+
}
\ No newline at end of file
diff --git a/feature/notetaking/src/main/java/com/iguana/notetaking/recording/RecordingService.kt b/feature/notetaking/src/main/java/com/iguana/notetaking/recording/RecordingService.kt
new file mode 100644
index 0000000..f735eb6
--- /dev/null
+++ b/feature/notetaking/src/main/java/com/iguana/notetaking/recording/RecordingService.kt
@@ -0,0 +1,148 @@
+package com.iguana.notetaking.recording
+
+import android.Manifest
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.app.Service
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.media.MediaRecorder
+import android.os.IBinder
+import android.util.Log
+import androidx.core.app.NotificationCompat
+import androidx.core.content.ContextCompat
+import java.io.IOException
+
+
+class RecordingService : Service() {
+
+ private var recorder: MediaRecorder? = null
+ private var isRecording = false
+ private val channelId = "RecordingServiceChannel"
+ private val notificationId = 1
+ private var outputFilePath: String? = null
+
+ override fun onCreate() {
+ super.onCreate()
+ createNotificationChannel()
+ }
+
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+ val action = intent?.action
+ Log.d("RecordingService", "onStartCommand: $action")
+
+ when (action) {
+ "START_RECORDING" -> startRecording()
+ "STOP_RECORDING" -> stopRecording()
+ }
+
+ return START_STICKY
+ }
+
+ private fun startRecording() {
+ Log.d("RecordingService", "녹음이 시작되기 바로 직전입니다.")
+
+ if (isRecording) {
+ Log.w("RecordingService", "녹음이 이미 진행 중입니다.")
+ return
+ }
+
+ // 권한 확인
+ if (ContextCompat.checkSelfPermission(
+ this, Manifest.permission.RECORD_AUDIO
+ ) != PackageManager.PERMISSION_GRANTED
+ ) {
+ Log.e("RecordingService", "녹음 권한이 없습니다.")
+ return
+ }
+
+ outputFilePath = getRecordingFilePath(this)
+ if (outputFilePath == null) {
+ Log.e("RecordingService", "녹음 파일 경로를 생성할 수 없습니다.")
+ return
+ }
+
+ recorder = MediaRecorder().apply {
+ try {
+ setAudioSource(MediaRecorder.AudioSource.MIC)
+ setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP)
+ setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB)
+ setOutputFile(outputFilePath)
+
+ prepare()
+ start()
+ isRecording = true
+ startForeground(notificationId, createRecordingNotification("녹음 중..."))
+ } catch (e: IllegalStateException) {
+ Log.e("RecordingService", "설정 오류: ${e.message}")
+ releaseRecorder()
+ } catch (e: IOException) {
+ Log.e("RecordingService", "prepare() 실패: ${e.message}")
+ releaseRecorder()
+ }
+ }
+ }
+
+ private fun releaseRecorder() {
+ recorder?.release()
+ recorder = null
+ isRecording = false
+ }
+
+ private fun stopRecording() {
+ if (!isRecording) {
+ Log.w("RecordingService", "현재 녹음이 진행 중이지 않습니다.")
+ return
+ }
+
+ recorder?.apply {
+ stop()
+ release()
+ }
+ recorder = null
+ isRecording = false
+ stopForeground(true)
+ sendRecordingFinishedBroadcast()
+ stopSelf()
+ }
+
+ private fun createRecordingNotification(contentText: String): Notification {
+ return NotificationCompat.Builder(this, channelId).setContentTitle("녹음 서비스")
+ .setContentText(contentText)
+ .setSmallIcon(com.iguana.designsystem.R.drawable.ic_record_active)
+ .setPriority(NotificationCompat.PRIORITY_LOW).build()
+ }
+
+ private fun createNotificationChannel() {
+ val channel = NotificationChannel(
+ channelId, "녹음 서비스 채널", NotificationManager.IMPORTANCE_LOW
+ )
+
+ val notificationManager = getSystemService(NotificationManager::class.java)
+ notificationManager.createNotificationChannel(channel)
+ }
+
+ override fun onBind(intent: Intent?): IBinder? {
+ return null
+ }
+
+ private fun getRecordingFilePath(context: Context): String {
+ // 외부 저장소의 앱 전용 디렉토리
+ val directory = context.getExternalFilesDir(null)
+ Log.d("RecordingService", "녹음 파일 경로: ${directory?.absolutePath}")
+ return "${directory?.absolutePath}/recording_${System.currentTimeMillis()}.3gp"
+ }
+
+ private fun sendRecordingFinishedBroadcast() {
+ outputFilePath?.let { path ->
+ val intent = Intent("com.iguana.notetaking.RECORDING_FINISHED").apply {
+ putExtra("filePath", path)
+ putExtra("fileName", path.substringAfterLast("/"))
+ }
+ sendBroadcast(intent)
+ }
+ }
+
+}
diff --git a/feature/notetaking/src/main/java/com/iguana/notetaking/sidebar/SideBarFragment.kt b/feature/notetaking/src/main/java/com/iguana/notetaking/sidebar/SideBarFragment.kt
index 5939323..b04d3a9 100644
--- a/feature/notetaking/src/main/java/com/iguana/notetaking/sidebar/SideBarFragment.kt
+++ b/feature/notetaking/src/main/java/com/iguana/notetaking/sidebar/SideBarFragment.kt
@@ -1,86 +1,100 @@
-package com.iguana.notetaking.sidebar
-
-import androidx.fragment.app.viewModels
-import android.os.Bundle
-import androidx.fragment.app.Fragment
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.core.os.bundleOf
-import com.google.android.material.tabs.TabLayoutMediator
-import com.iguana.notetaking.ai.AiFragment
-import com.iguana.notetaking.databinding.FragmentSideBarBinding
-import com.iguana.notetaking.recording.RecordFragment
-import dagger.hilt.android.AndroidEntryPoint
-
-@AndroidEntryPoint
-class SideBarFragment() : Fragment() {
-
- companion object {
- const val DOCUMENT_ID = "documentId"
- const val CURRENT_PAGE = "currentPage"
- fun newInstance(documentId: Long, currentPage: Int) = SideBarFragment().apply {
- arguments = bundleOf(
- DOCUMENT_ID to documentId,
- CURRENT_PAGE to currentPage
- )
+ package com.iguana.notetaking.sidebar
+
+ import androidx.fragment.app.viewModels
+ import android.os.Bundle
+ import androidx.fragment.app.Fragment
+ import android.view.LayoutInflater
+ import android.view.View
+ import android.view.ViewGroup
+ import androidx.core.os.bundleOf
+ import com.google.android.material.tabs.TabLayoutMediator
+ import com.iguana.notetaking.R
+ import com.iguana.notetaking.ai.AiFragment
+ import com.iguana.notetaking.databinding.FragmentSideBarBinding
+ import com.iguana.notetaking.recording.RecordFragment
+ import dagger.hilt.android.AndroidEntryPoint
+
+ @AndroidEntryPoint
+ class SideBarFragment() : Fragment() {
+
+ companion object {
+ const val DOCUMENT_ID = "documentId"
+ const val CURRENT_PAGE = "currentPage"
+ fun newInstance(documentId: Long, currentPage: Int) = SideBarFragment().apply {
+ arguments = bundleOf(
+ DOCUMENT_ID to documentId,
+ CURRENT_PAGE to currentPage
+ )
+ }
}
- }
- private val viewModel: SideBarViewModel by viewModels()
- private var _binding: FragmentSideBarBinding? = null
- private val binding get() = _binding!!
+ private val viewModel: SideBarViewModel by viewModels()
+ private var _binding: FragmentSideBarBinding? = null
+ private val binding get() = _binding!!
- override fun onCreateView(
- inflater: LayoutInflater, container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View {
- _binding = FragmentSideBarBinding.inflate(inflater, container, false)
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ _binding = FragmentSideBarBinding.inflate(inflater, container, false)
- arguments?.let {
- viewModel.documentId = it.getLong(DOCUMENT_ID)
- viewModel.setPageNumber(it.getInt(CURRENT_PAGE))
+ arguments?.let {
+ viewModel.documentId = it.getLong(DOCUMENT_ID)
+ viewModel.setPageNumber(it.getInt(CURRENT_PAGE))
+ }
+
+ return binding.root
}
- return binding.root
- }
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
+ val viewPager = binding.sideBarViewPager
+ val tabLayout = binding.sideBarTabLayout
- val viewPager = binding.sideBarViewPager
- val tabLayout = binding.sideBarTabLayout
+ viewPager.adapter = SidebarAdapter(this, viewModel.documentId, viewModel.pageNumber.value ?: 0)
- viewPager.adapter = SidebarAdapter(this, viewModel.documentId, viewModel.pageNumber.value ?: 0)
+ // 탭 레이아웃과 뷰페이저 연결
+ TabLayoutMediator(tabLayout, viewPager) { tab, position ->
+ tab.text = when (position) {
+ 0 -> "녹음"
+ 1 -> "AI"
+ else -> throw IllegalArgumentException("Invalid position")
+ }
+ }.attach()
- // 탭 레이아웃과 뷰페이저 연결
- TabLayoutMediator(tabLayout, viewPager) { tab, position ->
- tab.text = when (position) {
- 0 -> "녹음"
- 1 -> "AI"
- else -> throw IllegalArgumentException("Invalid position")
+ // 페이지 번호 변경 시 호출되는 메서드
+ viewModel.pageNumber.observe(viewLifecycleOwner) { pageNumber ->
+ (viewPager.adapter as SidebarAdapter).getFragment(0)?.let {
+ (it as RecordFragment).updateContentForPage(pageNumber)
+ }
+ (viewPager.adapter as SidebarAdapter).getFragment(1)?.let {
+ (it as AiFragment).updateContentForPage(pageNumber)
+ }
}
- }.attach()
-
- // 페이지 번호 변경 시 호출되는 메서드
- viewModel.pageNumber.observe(viewLifecycleOwner) { pageNumber ->
- (viewPager.adapter as SidebarAdapter).getFragment(0)?.let {
- (it as RecordFragment).updateContentForPage(pageNumber)
+ }
+ // RecordFragment에서 녹음 시작
+ fun startRecordingInRecordFragment() {
+ (binding.sideBarViewPager.adapter as SidebarAdapter).getFragment(0)?.let {
+ (it as RecordFragment).startRecording(requireContext())
}
- (viewPager.adapter as SidebarAdapter).getFragment(1)?.let {
- (it as AiFragment).updateContentForPage(pageNumber)
+ }
+
+ // RecordFragment에서 녹음 중지
+ fun stopRecordingInRecordFragment() {
+ (binding.sideBarViewPager.adapter as SidebarAdapter).getFragment(0)?.let {
+ (it as RecordFragment).stopRecording(requireContext())
}
}
- }
-
- // 사이드바 내용 업데이트 메서드
- fun updatePageNumber(pageNumber: Int) {
- viewModel.setPageNumber(pageNumber)
- }
-
- override fun onDestroyView() {
- super.onDestroyView()
- _binding = null
- }
-}
\ No newline at end of file
+
+ // 사이드바 내용 업데이트 메서드
+ fun updatePageNumber(pageNumber: Int) {
+ viewModel.setPageNumber(pageNumber)
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+ }
\ No newline at end of file
diff --git a/feature/notetaking/src/main/res/layout-sw600dp/titlebar.xml b/feature/notetaking/src/main/res/layout-sw600dp/titlebar.xml
index 179df81..4a2f9df 100644
--- a/feature/notetaking/src/main/res/layout-sw600dp/titlebar.xml
+++ b/feature/notetaking/src/main/res/layout-sw600dp/titlebar.xml
@@ -1,6 +1,5 @@
-
-
+
\ No newline at end of file
diff --git a/feature/notetaking/src/main/res/layout/activity_notetaking.xml b/feature/notetaking/src/main/res/layout/activity_notetaking.xml
index 582fc98..c32d276 100644
--- a/feature/notetaking/src/main/res/layout/activity_notetaking.xml
+++ b/feature/notetaking/src/main/res/layout/activity_notetaking.xml
@@ -3,9 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
-
-
diff --git a/feature/notetaking/src/main/res/layout/fragment_record.xml b/feature/notetaking/src/main/res/layout/fragment_record.xml
index 799c21d..5e501ed 100644
--- a/feature/notetaking/src/main/res/layout/fragment_record.xml
+++ b/feature/notetaking/src/main/res/layout/fragment_record.xml
@@ -1,11 +1,14 @@
+
+
+
@@ -15,40 +18,67 @@
android:id="@+id/boxView"
android:layout_width="180dp"
android:layout_height="36dp"
- android:padding="8dp"
+ android:layout_marginTop="20dp"
android:background="@drawable/page_box_background"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintStart_toStartOf="parent"
+ android:padding="8dp"
app:layout_constraintEnd_toEndOf="parent"
- android:layout_marginTop="20dp">
-
-
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent">
+
+
+ app:layout_constraintTop_toBottomOf="@id/boxView" />
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/feature/notetaking/src/main/res/layout/titlebar.xml b/feature/notetaking/src/main/res/layout/titlebar.xml
index 6578455..28e5f65 100644
--- a/feature/notetaking/src/main/res/layout/titlebar.xml
+++ b/feature/notetaking/src/main/res/layout/titlebar.xml
@@ -1,6 +1,7 @@
+
-
-
diff --git a/feature/notetaking/src/main/res/values/strings.xml b/feature/notetaking/src/main/res/values/strings.xml
index b28c835..401fb6e 100644
--- a/feature/notetaking/src/main/res/values/strings.xml
+++ b/feature/notetaking/src/main/res/values/strings.xml
@@ -11,4 +11,7 @@
AI 요약이 실패했습니다.
상태를 확인할 수 없습니다.
요약 데이터가 없습니다.
+ 무제
+ 녹음 중이 아닙니다.
+ 녹음 중..
\ No newline at end of file