Skip to content
Draft
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
13 changes: 2 additions & 11 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -75,25 +75,16 @@ dependencies {

//compose
implementation(platform(Dependencies.Compose.BOM))
androidTestImplementation(platform(Dependencies.Compose.BOM))
implementation(Dependencies.Compose.MATERIAL3)

// Android Studio Preview support
debugImplementation(Dependencies.Compose.UI_TOOLING_PREVIEW)
debugImplementation(Dependencies.Compose.UI_TOOLING)
// UI Tests
androidTestImplementation(Dependencies.Compose.UI_TEST_JUNIT4)
debugImplementation(Dependencies.Compose.UI_TEST_MANIFEST)
// Optional - Integration with LiveData
implementation(Dependencies.Compose.LIVEDATA)
// Optional - Integration with activities
// Integration with activities
implementation(Dependencies.Compose.ACTIVITY_COMPOSE)
// Optional - Integration with ViewModels
// Integration with ViewModels
implementation(Dependencies.Compose.LIFECYCLE_VIEWMODEL_COMPOSE)

//testing
testImplementation(Dependencies.Testing.JUNIT)
testImplementation(Dependencies.Testing.JUNIT_JUPITER)
androidTestImplementation(Dependencies.Testing.ANDROIDX_TEST_EXT)
androidTestImplementation(Dependencies.Testing.ANDROIDX_TEST_ESPRESSO)
}

This file was deleted.

27 changes: 24 additions & 3 deletions app/src/main/java/com/critt/interp/ui/MainViewModel.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package com.critt.interp.ui

import android.annotation.SuppressLint
import androidx.annotation.RequiresPermission
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.critt.data.ApiResult
import com.critt.data.AudioRecorderFactory
import com.critt.data.AudioSource
import com.critt.data.LanguageRepository
import com.critt.domain.LanguageData
Expand All @@ -23,10 +26,13 @@ import javax.inject.Inject

@HiltViewModel
class MainViewModel @Inject constructor(
private val audioSource: AudioSource,
private val audioRecorderFactory: AudioRecorderFactory,
private val translationSource: TranslationSource,
private val languageRepo: LanguageRepository
) : ViewModel() {
// AudioSource
lateinit var audioSource: AudioSource

// Supported languages state
private val _supportedLanguages =
MutableStateFlow<ApiResult<List<LanguageData>>>(ApiResult.Loading)
Expand Down Expand Up @@ -98,6 +104,13 @@ class MainViewModel @Inject constructor(
}
}

@RequiresPermission(android.Manifest.permission.RECORD_AUDIO)
private fun initAudioSource() {
if (!::audioSource.isInitialized) {
audioSource = AudioSource(audioRecorderFactory.create())
}
}

/**
* Cleans up resources when the ViewModel is cleared.
*
Expand Down Expand Up @@ -169,16 +182,22 @@ class MainViewModel @Inject constructor(
*
* It starts or stops recording and streaming based on the current state.
*/
@RequiresPermission(android.Manifest.permission.RECORD_AUDIO)
fun toggleStreaming() {
when (_streamingState.value) {
is AudioStreamingState.Idle -> startRecordingAndStreaming()
is AudioStreamingState.Idle -> {
initAudioSource()
startRecordingAndStreaming()
}

is AudioStreamingState.Streaming -> {
stopRecordingAndStreaming()
_streamingState.update { AudioStreamingState.Idle }
}

is AudioStreamingState.Error -> {
stopRecordingAndStreaming()
initAudioSource()
startRecordingAndStreaming()
}
}
Expand Down Expand Up @@ -262,7 +281,9 @@ class MainViewModel @Inject constructor(
* or when an error occurs that requires stopping the streaming process.
*/
private fun stopRecordingAndStreaming() {
audioSource.stopRecording()
if (::audioSource.isInitialized) {
audioSource.stopRecording()
}
translationSource.disconnectAllSockets()
}

Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ plugins {
id("com.android.application") version Dependencies.PluginVersions.ANDROID_GRADLE apply false
id("com.android.library") version Dependencies.PluginVersions.ANDROID_GRADLE apply false
id("org.jetbrains.kotlin.android") version Dependencies.PluginVersions.KOTLIN apply false
kotlin("plugin.serialization") version Dependencies.PluginVersions.SERIALIZATION apply false
// kotlin("plugin.serialization") version Dependencies.PluginVersions.SERIALIZATION apply false
id("com.google.dagger.hilt.android") version Dependencies.PluginVersions.HILT apply false
id("com.google.gms.google-services") version Dependencies.PluginVersions.GOOGLE_SERVICES apply false
}
36 changes: 28 additions & 8 deletions buildSrc/src/main/kotlin/Dependencies.kt
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,8 @@ object Dependencies {
object Compose {
const val BOM = "androidx.compose:compose-bom:2024.10.01"
const val MATERIAL3 = "androidx.compose.material3:material3"
const val LIVEDATA = "androidx.compose.runtime:runtime-livedata"
const val UI_TOOLING_PREVIEW = "androidx.compose.ui:ui-tooling-preview"
const val UI_TOOLING = "androidx.compose.ui:ui-tooling"
const val UI_TEST_JUNIT4 = "androidx.compose.ui:ui-test-junit4"
const val UI_TEST_MANIFEST = "androidx.compose.ui:ui-test-manifest"

const val ACTIVITY_COMPOSE = "androidx.activity:activity-compose:1.10.0"
const val LIFECYCLE_VIEWMODEL_COMPOSE = "androidx.lifecycle:lifecycle-viewmodel-compose:2.8.7"
}
Expand All @@ -50,10 +46,34 @@ object Dependencies {
}

object Testing {
const val JUNIT = "junit:junit:4.13.2"
const val JUNIT_JUPITER = "org.junit.jupiter:junit-jupiter:5.8.1"
const val ANDROIDX_TEST_EXT = "androidx.test.ext:junit:1.2.1"
const val ANDROIDX_TEST_ESPRESSO = "androidx.test.espresso:espresso-core:3.6.1"
// JUnit
private const val JUNIT_VERSION = "4.13.2"
const val JUNIT = "junit:junit:${JUNIT_VERSION}"

// Mockito
private const val MOCKITO_VERSION = "5.2.0"
private const val MOCKITO_KOTLIN_VERSION = "5.1.0"
const val MOCKITO_CORE = "org.mockito:mockito-core:${MOCKITO_VERSION}"
const val MOCKITO_INLINE = "org.mockito:mockito-inline:${MOCKITO_VERSION}"
const val MOCKITO_KOTLIN = "org.mockito.kotlin:mockito-kotlin:${MOCKITO_KOTLIN_VERSION}"

// Truth
private const val TRUTH_VERSION = "1.1.5"
const val TRUTH = "com.google.truth:truth:${TRUTH_VERSION}"

// Coroutines test support
private const val COROUTINES_TEST_VERSION = "1.7.3"
const val COROUTINES_TEST = "org.jetbrains.kotlinx:kotlinx-coroutines-test:${COROUTINES_TEST_VERSION}"

// AndroidX Test library for testing Android components
private const val ANDROIDX_TEST_VERSION = "1.5.0"
private const val ANDROIDX_TEST_EXT_VERSION = "1.1.5"

const val ANDROIDX_TEST_CORE = "androidx.test:core:${ANDROIDX_TEST_VERSION}"
const val ANDROIDX_TEST_RULES = "androidx.test:rules:${ANDROIDX_TEST_VERSION}"
const val ANDROIDX_TEST_EXT_JUNIT = "androidx.test.ext:junit:${ANDROIDX_TEST_EXT_VERSION}"
const val ANDROIDX_TEST_EXT_JUNIT_KTX = "androidx.test.ext:junit-ktx:${ANDROIDX_TEST_EXT_VERSION}"
const val ANDROIDX_TEST_RUNNER = "androidx.test:runner:${ANDROIDX_TEST_VERSION}"
}

object Crypto {
Expand Down
1 change: 0 additions & 1 deletion core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,4 @@ android {
dependencies {
//testing
testImplementation(Dependencies.Testing.JUNIT)
testImplementation(Dependencies.Testing.JUNIT_JUPITER)
}
33 changes: 29 additions & 4 deletions data/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ android {
dependencies {
implementation(project(":domain"))
implementation(project(":core"))
testImplementation(kotlin("test"))

implementation(Dependencies.SocketIO.SOCKET_IO) {
exclude(group = "org.json", module = "json")
Expand All @@ -66,7 +67,31 @@ dependencies {
kapt(Dependencies.Hilt.ANDROID_COMPILER)
implementation(Dependencies.Hilt.ANDROID)

//testing
testImplementation(Dependencies.Testing.JUNIT)
testImplementation(Dependencies.Testing.JUNIT_JUPITER)
}
//testing - JUnit
//testImplementation(Dependencies.Testing.JUNIT)
testImplementation("org.junit.jupiter:junit-jupiter:5.8.1")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1")
//testing - Mockito
testImplementation(Dependencies.Testing.MOCKITO_CORE)
testImplementation(Dependencies.Testing.MOCKITO_INLINE)
testImplementation(Dependencies.Testing.MOCKITO_KOTLIN)
//testing - Truth
testImplementation(Dependencies.Testing.TRUTH)
//testing - support
testImplementation(Dependencies.Testing.COROUTINES_TEST)
testImplementation(Dependencies.Testing.ANDROIDX_TEST_CORE)
testImplementation(Dependencies.Testing.ANDROIDX_TEST_RULES)
testImplementation(Dependencies.Testing.ANDROIDX_TEST_RUNNER)
testImplementation(Dependencies.Testing.ANDROIDX_TEST_EXT_JUNIT)
testImplementation(Dependencies.Testing.ANDROIDX_TEST_EXT_JUNIT_KTX)
}



tasks.withType<Test> {
useJUnitPlatform()

testLogging { // This is for logging and can be removed.
events("passed", "skipped", "failed")
}
}
68 changes: 68 additions & 0 deletions data/src/main/java/com/critt/data/AudioRecorder.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.critt.data

import android.media.AudioFormat
import android.media.AudioRecord
import android.media.MediaRecorder
import androidx.annotation.RequiresPermission


class AudioRecorder(val audioRecord: AudioRecord, override val bufferSize: Int) : IAudioRecorder {
override val recordingState: Int
get() = audioRecord.recordingState
override val state: Int
get() = audioRecord.state

override fun startRecording() {
audioRecord.startRecording()
}

override fun stop() {
audioRecord.stop()
}

override fun read(audioBuffer: ByteArray, offsetInBytes: Int, sizeInBytes: Int): Int {
return audioRecord.read(audioBuffer, offsetInBytes, sizeInBytes)
}

override fun release() {
audioRecord.release()
}
}

interface IAudioRecorder {
val bufferSize: Int
val recordingState: Int
val state: Int
fun startRecording()
fun stop()
fun read(audioBuffer: ByteArray, offsetInBytes: Int, sizeInBytes: Int): Int
fun release()
}

class AudioRecorderFactory {
@RequiresPermission(android.Manifest.permission.RECORD_AUDIO)
fun create(
audioSource: Int = MediaRecorder.AudioSource.MIC,
sampleRateInHz: Int = 16000,
channelConfig: Int = AudioFormat.CHANNEL_IN_MONO,
audioFormat: Int = AudioFormat.ENCODING_PCM_16BIT,
minBufferSize: Int = 2048
): IAudioRecorder {
val minDeviceBufferSize: Int = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat)
when (minDeviceBufferSize) {
AudioRecord.ERROR_BAD_VALUE -> throw UnsupportedOperationException("Recording parameters not supported")
AudioRecord.ERROR -> throw RuntimeException("Failed to query device")
}

return AudioRecorder(
AudioRecord(
audioSource,
sampleRateInHz,
channelConfig,
audioFormat,
minDeviceBufferSize.coerceAtLeast(minBufferSize)
),
minDeviceBufferSize.coerceAtLeast(minBufferSize)
)
}
}
Loading